refactor(editor): rename presets to integration test (#10340)

This commit is contained in:
Saul-Mirone
2025-02-21 06:26:03 +00:00
parent f79324b6a1
commit f3218ab3bc
116 changed files with 156 additions and 210 deletions

View File

@@ -15,7 +15,7 @@ import {
titleMiddleware,
} from '@blocksuite/blocks';
import { WithDisposable } from '@blocksuite/global/utils';
import type { AffineEditorContainer } from '@blocksuite/presets';
import type { TestAffineEditorContainer } from '@blocksuite/integration-test';
import { type DocSnapshot, Transformer } from '@blocksuite/store';
import { effect } from '@preact/signals-core';
import type SlTabPanel from '@shoelace-style/shoelace/dist/components/tab-panel/tab-panel.js';
@@ -284,7 +284,7 @@ export class AdaptersPanel extends WithDisposable(ShadowlessElement) {
private accessor _plainTextContent = '';
@property({ attribute: false })
accessor editor!: AffineEditorContainer;
accessor editor!: TestAffineEditorContainer;
}
declare global {

View File

@@ -1,6 +1,6 @@
import { ShadowlessElement } from '@blocksuite/block-std';
import { WithDisposable } from '@blocksuite/global/utils';
import type { AffineEditorContainer } from '@blocksuite/presets';
import type { TestAffineEditorContainer } from '@blocksuite/integration-test';
import { effect } from '@preact/signals-core';
import { css, html, nothing } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
@@ -59,7 +59,7 @@ export class CustomFramePanel extends WithDisposable(ShadowlessElement) {
private accessor _show = false;
@property({ attribute: false })
accessor editor!: AffineEditorContainer;
accessor editor!: TestAffineEditorContainer;
}
declare global {

View File

@@ -1,6 +1,6 @@
import { ShadowlessElement } from '@blocksuite/block-std';
import { WithDisposable } from '@blocksuite/global/utils';
import type { AffineEditorContainer } from '@blocksuite/presets';
import type { TestAffineEditorContainer } from '@blocksuite/integration-test';
import { css, html, nothing } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
@@ -45,7 +45,7 @@ export class CustomOutlinePanel extends WithDisposable(ShadowlessElement) {
private accessor _show = false;
@property({ attribute: false })
accessor editor!: AffineEditorContainer;
accessor editor!: TestAffineEditorContainer;
}
declare global {

View File

@@ -1,5 +1,5 @@
import { WithDisposable } from '@blocksuite/global/utils';
import type { AffineEditorContainer } from '@blocksuite/presets';
import type { TestAffineEditorContainer } from '@blocksuite/integration-test';
import { css, html, LitElement, nothing } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
@@ -38,7 +38,7 @@ export class CustomOutlineViewer extends WithDisposable(LitElement) {
private accessor _show = false;
@property({ attribute: false })
accessor editor!: AffineEditorContainer;
accessor editor!: TestAffineEditorContainer;
@property({ attribute: false })
accessor toggleOutlinePanel: (() => void) | null = null;

View File

@@ -5,7 +5,7 @@ import {
GenerateDocUrlProvider,
} from '@blocksuite/blocks';
import { WithDisposable } from '@blocksuite/global/utils';
import type { AffineEditorContainer } from '@blocksuite/presets';
import type { TestAffineEditorContainer } from '@blocksuite/integration-test';
import type { Doc, Workspace } from '@blocksuite/store';
import { css, html, nothing } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@@ -163,7 +163,7 @@ export class DocsPanel extends WithDisposable(ShadowlessElement) {
}
@property({ attribute: false })
accessor editor!: AffineEditorContainer;
accessor editor!: TestAffineEditorContainer;
@property({ attribute: false })
accessor onClose!: () => void;

View File

@@ -48,7 +48,7 @@ import {
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import type { SerializedXYWH } from '@blocksuite/global/utils';
import type { DeltaInsert } from '@blocksuite/inline/types';
import { AffineEditorContainer, type CommentPanel } from '@blocksuite/presets';
import { TestAffineEditorContainer } from '@blocksuite/integration-test';
import { Text, Transformer, type Workspace } from '@blocksuite/store';
import type { SlDropdown } from '@shoelace-style/shoelace';
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js';
@@ -57,6 +57,7 @@ import { customElement, property, query, state } from 'lit/decorators.js';
import * as lz from 'lz-string';
import type { Pane } from 'tweakpane';
import type { CommentPanel } from '../../comment/index.js';
import { createTestEditor } from '../../starter/utils/extensions.js';
import { mockEdgelessTheme } from '../mock-services.js';
import { AdaptersPanel } from './adapters-panel.js';
@@ -647,7 +648,7 @@ export class StarterDebugMenu extends ShadowlessElement {
const newEditor = createTestEditor(this.doc, this.collection);
app.append(newEditor);
app.childNodes.forEach(child => {
if (child instanceof AffineEditorContainer) {
if (child instanceof TestAffineEditorContainer) {
child.style.flex = '1';
}
});
@@ -1014,7 +1015,7 @@ export class StarterDebugMenu extends ShadowlessElement {
accessor docsPanel!: DocsPanel;
@property({ attribute: false })
accessor editor!: AffineEditorContainer;
accessor editor!: TestAffineEditorContainer;
@property({ attribute: false })
accessor framePanel!: CustomFramePanel;

View File

@@ -1,6 +1,6 @@
import type { DocModeProvider } from '@blocksuite/blocks';
import { assertExists } from '@blocksuite/global/utils';
import type { AffineEditorContainer } from '@blocksuite/presets';
import type { TestAffineEditorContainer } from '@blocksuite/integration-test';
import type { Doc, Store, Workspace } from '@blocksuite/store';
export function getDocFromUrlParams(collection: Workspace, url: URL) {
@@ -40,7 +40,7 @@ export function setDocModeFromUrlParams(
export function listenHashChange(
collection: Workspace,
editor: AffineEditorContainer
editor: TestAffineEditorContainer
) {
const panel = document.querySelector('docs-panel');
window.addEventListener('hashchange', () => {

View File

@@ -12,7 +12,7 @@ import {
toast,
} from '@blocksuite/blocks';
import { Slot } from '@blocksuite/global/utils';
import type { AffineEditorContainer } from '@blocksuite/presets';
import type { TestAffineEditorContainer } from '@blocksuite/integration-test';
import { type Workspace } from '@blocksuite/store';
import { Signal, signal } from '@preact/signals-core';
@@ -37,7 +37,7 @@ export function removeModeFromStorage(docId: string) {
const DEFAULT_MODE: DocMode = 'page';
const slotMap = new Map<string, Slot<DocMode>>();
export function mockDocModeService(editor: AffineEditorContainer) {
export function mockDocModeService(editor: TestAffineEditorContainer) {
const getEditorModeCallback: () => DocMode = () => editor.mode;
const setEditorModeCallback: (mode: DocMode) => void = mode =>
editor.switchEditor(mode);
@@ -78,7 +78,7 @@ export function mockDocModeService(editor: AffineEditorContainer) {
return docModeService;
}
export function mockNotificationService(editor: AffineEditorContainer) {
export function mockNotificationService(editor: TestAffineEditorContainer) {
const notificationService: NotificationService = {
toast: (message, options) => {
toast(editor.host!, message, options?.duration);

View File

@@ -0,0 +1,123 @@
import { ShadowlessElement, TextSelection } from '@blocksuite/block-std';
import type { RichText } from '@blocksuite/blocks';
import { WithDisposable } from '@blocksuite/global/utils';
import { css, html, nothing } from 'lit';
import { property, query } from 'lit/decorators.js';
import * as Y from 'yjs';
import type { Comment, CommentManager } from './comment-manager.js';
export class CommentInput extends WithDisposable(ShadowlessElement) {
static override styles = css`
.comment-input-container {
padding: 16px;
}
.comment-quote {
font-size: 10px;
color: var(--affine-text-secondary-color);
padding-left: 8px;
border-left: 2px solid var(--affine-text-secondary-color);
margin-bottom: 8px;
}
.comment-author {
font-size: 12px;
}
.comment-editor {
white-space: pre-wrap;
overflow-wrap: break-word;
min-height: 24px;
margin-top: 16px;
margin-bottom: 16px;
}
.comment-control {
display: flex;
gap: 8px;
margin-top: 8px;
}
`;
private readonly _cancel = () => {
this.remove();
};
private readonly _submit = (textSelection: TextSelection) => {
const deltas = this._editor.inlineEditor?.yTextDeltas;
if (!deltas) {
this.remove();
return;
}
const yText = new Y.Text();
yText.applyDelta(deltas);
const comment = this.manager.addComment(textSelection, {
author: 'Anonymous',
text: yText,
});
this.onSubmit?.(comment);
this.remove();
};
get host() {
return this.manager.host;
}
override render() {
const textSelection = this.host.selection.find(TextSelection);
if (!textSelection) {
this.remove();
return nothing;
}
const parseResult = this.manager.parseTextSelection(textSelection);
if (!parseResult) {
this.remove();
return nothing;
}
const { quote } = parseResult;
const tmpYDoc = new Y.Doc();
const tmpYText = tmpYDoc.getText('comment');
return html`<div class="comment-input-container">
<div class="comment-state">
<div class="comment-quote">${quote}</div>
<div class="comment-author">Anonymous</div>
</div>
<rich-text
@blur=${() => this._submit(textSelection)}
.yText=${tmpYText}
class="comment-editor"
></rich-text>
<div class="comment-control">
<button
@click=${() => this._submit(textSelection)}
class="comment-submit"
>
Submit
</button>
<button @click=${this._cancel} class="comment-cancel">Cancel</button>
</div>
</div>`;
}
@query('rich-text')
private accessor _editor!: RichText;
@property({ attribute: false })
accessor manager!: CommentManager;
@property({ attribute: false })
accessor onSubmit: undefined | ((comment: Comment) => void) = undefined;
}
declare global {
interface HTMLElementTagNameMap {
'comment-input': CommentInput;
}
}

View File

@@ -0,0 +1,160 @@
import { getSelectedBlocksCommand } from '@blocksuite/affine-shared/commands';
import type { EditorHost, TextSelection } from '@blocksuite/block-std';
import * as Y from 'yjs';
export interface CommentMeta {
id: string;
date: number;
}
export interface CommentRange {
start: {
id: string;
index: Y.RelativePosition;
};
end: {
id: string;
index: Y.RelativePosition;
};
}
export interface CommentContent {
quote: string;
author: string;
text: Y.Text;
}
export type Comment = CommentMeta & CommentRange & CommentContent;
export class CommentManager {
private get _command() {
return this.host.command;
}
get commentsMap() {
return this.host.doc.spaceDoc.getMap<Y.Map<unknown>>('comments');
}
constructor(readonly host: EditorHost) {}
addComment(
selection: TextSelection,
payload: Pick<CommentContent, 'author' | 'text'>
): Comment {
const parseResult = this.parseTextSelection(selection);
if (!parseResult) {
throw new Error('Invalid selection');
}
const { quote, range } = parseResult;
const id = this.host.doc.workspace.idGenerator();
const comment: Comment = {
id,
date: Date.now(),
start: range.start,
end: range.end,
quote,
...payload,
};
this.commentsMap.set(id, new Y.Map<unknown>(Object.entries(comment)));
return comment;
}
getComments(): Comment[] {
const comments: Comment[] = [];
this.commentsMap.forEach((comment, key) => {
const start = comment.get('start') as Comment['start'];
const end = comment.get('end') as Comment['end'];
const startIndex = Y.createAbsolutePositionFromRelativePosition(
start.index,
this.host.doc.spaceDoc
);
const startBlock = this.host.view.getBlock(start.id);
const endIndex = Y.createAbsolutePositionFromRelativePosition(
end.index,
this.host.doc.spaceDoc
);
const endBlock = this.host.view.getBlock(end.id);
if (!startIndex || !startBlock || !endIndex || !endBlock) {
// remove outdated comment
this.commentsMap.delete(key);
return;
}
const result: Comment = {
id: comment.get('id') as Comment['id'],
date: comment.get('date') as Comment['date'],
start,
end,
quote: comment.get('quote') as Comment['quote'],
author: comment.get('author') as Comment['author'],
text: comment.get('text') as Comment['text'],
};
comments.push(result);
});
return comments;
}
parseTextSelection(selection: TextSelection): {
quote: CommentContent['quote'];
range: CommentRange;
} | null {
const [_, ctx] = this._command
.chain()
.pipe(getSelectedBlocksCommand, {
currentTextSelection: selection,
types: ['text'],
})
.run();
const blocks = ctx.selectedBlocks;
if (!blocks || blocks.length === 0) return null;
const { from, to } = selection;
const fromBlock = blocks[0];
const fromBlockText = fromBlock.model.text;
const fromBlockId = fromBlock.model.id;
const toBlock = blocks[blocks.length - 1];
const toBlockText = toBlock.model.text;
const toBlockId = toBlock.model.id;
if (!fromBlockText || !toBlockText) return null;
const startIndex = Y.createRelativePositionFromTypeIndex(
fromBlockText.yText,
from.index
);
const endIndex = Y.createRelativePositionFromTypeIndex(
toBlockText.yText,
to ? to.index + to.length : from.index + from.length
);
const quote = blocks.reduce((acc, block, index) => {
const text = block.model.text;
if (!text) return acc;
if (index === 0) {
return (
acc +
text.yText.toString().slice(from.index, from.index + from.length)
);
}
if (index === blocks.length - 1 && to) {
return acc + ' ' + text.yText.toString().slice(0, to.index + to.length);
}
return acc + ' ' + text.yText.toString();
}, '');
return {
quote,
range: {
start: {
id: fromBlockId,
index: startIndex,
},
end: {
id: toBlockId,
index: endIndex,
},
},
};
}
}

View File

@@ -0,0 +1,116 @@
import { ShadowlessElement, TextSelection } from '@blocksuite/block-std';
import { WithDisposable } from '@blocksuite/global/utils';
import type { TestAffineEditorContainer } from '@blocksuite/integration-test';
import { css, html } from 'lit';
import { property, query } from 'lit/decorators.js';
import { CommentInput } from './comment-input.js';
import { CommentManager } from './comment-manager.js';
export class CommentPanel extends WithDisposable(ShadowlessElement) {
static override styles = css`
comment-panel {
position: absolute;
top: 0;
right: 0;
border: 1px solid var(--affine-border-color, #e3e2e4);
background-color: var(--affine-background-primary-color);
height: 100vh;
width: 320px;
box-sizing: border-box;
padding-top: 16px;
}
.comment-panel-container {
width: 100%;
height: 100%;
padding: 16px;
}
.comment-panel-head {
display: flex;
gap: 8px;
}
.comment-panel-comments {
margin-top: 16px;
}
.comment-panel-comment {
margin-bottom: 16px;
}
.comment-panel-comment-quote {
font-size: 10px;
color: var(--affine-text-secondary-color);
padding-left: 8px;
border-left: 2px solid var(--affine-text-secondary-color);
margin-bottom: 8px;
}
.comment-panel-comment-author {
font-size: 12px;
}
.comment-panel-comment-text {
margin-top: 8px;
}
`;
commentManager: CommentManager | null = null;
private _addComment() {
const textSelection = this.editor.host?.selection.find(TextSelection);
if (!textSelection) return;
const commentInput = new CommentInput();
if (!this.commentManager) return;
commentInput.manager = this.commentManager;
commentInput.onSubmit = () => {
this.requestUpdate();
};
this._container.append(commentInput);
}
override connectedCallback() {
super.connectedCallback();
if (!this.editor.host) return;
this.commentManager = new CommentManager(this.editor.host);
}
override render() {
if (!this.commentManager) return;
const comments = this.commentManager.getComments();
return html`<div class="comment-panel-container">
<div class="comment-panel-head">
<button @click=${this._addComment}>Add Comment</button>
</div>
<div class="comment-panel-comments">
${comments.map(comment => {
return html`<div class="comment-panel-comment">
<div class="comment-panel-comment-quote">${comment.quote}</div>
<div class="comment-panel-comment-author">${comment.author}</div>
<div class="comment-panel-comment-text">
<rich-text .yText=${comment.text} .readonly=${true}></rich-text>
</div>
</div>`;
})}
</div>
</div>`;
}
@query('.comment-panel-container')
private accessor _container!: HTMLDivElement;
@property({ attribute: false })
accessor editor!: TestAffineEditorContainer;
}
declare global {
interface HTMLElementTagNameMap {
'comment-panel': CommentPanel;
}
}

View File

@@ -0,0 +1,7 @@
import { CommentInput } from './comment-input.js';
import { CommentPanel } from './comment-panel.js';
export function effects() {
customElements.define('comment-input', CommentInput);
customElements.define('comment-panel', CommentPanel);
}

View File

@@ -0,0 +1,2 @@
export * from './comment-manager.js';
export * from './comment-panel.js';

View File

@@ -1,5 +1,5 @@
import type { EditorHost } from '@blocksuite/block-std';
import type { AffineEditorContainer } from '@blocksuite/presets';
import type { TestAffineEditorContainer } from '@blocksuite/integration-test';
import type { BlockSchema, Blocks, Workspace, Transformer } from '@blocksuite/store';
import type { z } from 'zod';
import type * as Y from 'yjs';
@@ -12,7 +12,7 @@ declare global {
];
interface Window {
editor: AffineEditorContainer;
editor: TestAffineEditorContainer;
doc: Blocks;
collection: Workspace;
blockSchemas: z.infer<typeof BlockSchema>[];

View File

@@ -4,12 +4,13 @@ import * as blockStd from '@blocksuite/block-std';
import * as blocks from '@blocksuite/blocks';
import { effects as blocksEffects } from '@blocksuite/blocks/effects';
import * as globalUtils from '@blocksuite/global/utils';
import * as editor from '@blocksuite/presets';
import { effects as presetsEffects } from '@blocksuite/presets/effects';
import * as editor from '@blocksuite/integration-test';
import { effects as presetsEffects } from '@blocksuite/integration-test/effects';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import * as store from '@blocksuite/store';
import { setupEdgelessTemplate } from '../_common/setup.js';
import { effects as commentEffects } from '../comment/effects.js';
import {
createStarterDocCollection,
initStarterDocCollection,
@@ -19,6 +20,7 @@ import { prepareTestApp } from './utils/test';
blocksEffects();
presetsEffects();
commentEffects();
async function main() {
if (window.collection) return;

View File

@@ -1,4 +1,3 @@
import { CommentPanel } from '@blocksuite/presets';
import type { Store, Workspace } from '@blocksuite/store';
import { AttachmentViewerPanel } from '../../_common/components/attachment-viewer-panel';
@@ -8,6 +7,7 @@ import { CustomOutlineViewer } from '../../_common/components/custom-outline-vie
import { DocsPanel } from '../../_common/components/docs-panel';
import { LeftSidePanel } from '../../_common/components/left-side-panel';
import { StarterDebugMenu } from '../../_common/components/starter-debug-menu';
import { CommentPanel } from '../../comment/comment-panel';
import { createTestEditor } from './extensions';
export async function createTestApp(doc: Store, collection: Workspace) {

View File

@@ -9,7 +9,7 @@ import {
ParseDocUrlExtension,
RefNodeSlotsProvider,
} from '@blocksuite/blocks';
import { type AffineEditorContainer } from '@blocksuite/presets';
import { type TestAffineEditorContainer } from '@blocksuite/integration-test';
import type { ExtensionType, Store, Workspace } from '@blocksuite/store';
import {
@@ -19,7 +19,7 @@ import {
} from '../../_common/mock-services';
export function getTestCommonExtensions(
editor: AffineEditorContainer
editor: TestAffineEditorContainer
): ExtensionType[] {
return [
FontConfigExtension(CommunityCanvasTextFonts),

View File

@@ -1,5 +1,5 @@
import { DocModeProvider } from '@blocksuite/blocks';
import { AffineEditorContainer } from '@blocksuite/presets';
import { TestAffineEditorContainer } from '@blocksuite/integration-test';
import type { Workspace } from '@blocksuite/store';
import {
@@ -27,7 +27,7 @@ export async function mountDefaultDocEditor(collection: Workspace) {
const init = params.get('init');
if (init && init.startsWith('multiple-editor')) {
app.childNodes.forEach(node => {
if (node instanceof AffineEditorContainer) {
if (node instanceof TestAffineEditorContainer) {
node.style.flex = '1';
if (init === 'multiple-editor-vertical') {
node.style.overflow = 'auto';

View File

@@ -18,7 +18,7 @@
"@blocksuite/global": "workspace:*",
"@blocksuite/icons": "^2.2.1",
"@blocksuite/inline": "workspace:*",
"@blocksuite/presets": "workspace:*",
"@blocksuite/integration-test": "workspace:*",
"@blocksuite/store": "workspace:*",
"@blocksuite/sync": "workspace:*",
"@preact/signals-core": "^1.8.0",

View File

@@ -16,7 +16,7 @@
{ "path": "../affine/data-view" },
{ "path": "../framework/global" },
{ "path": "../framework/inline" },
{ "path": "../presets" },
{ "path": "../integration-test" },
{ "path": "../framework/store" },
{ "path": "../framework/sync" }
]