From 19652cee26a7f524ec6b3544d67cc158e75d8796 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Wed, 20 May 2026 15:56:54 +0400 Subject: [PATCH] Refactor PasteComponent --- packages/core/src/commands/index.ts | 12 ++--- packages/core/src/commands/registry.ts | 4 +- .../core/src/commands/view/CopyComponent.ts | 1 - .../core/src/commands/view/PasteComponent.ts | 23 +++++--- .../specs/commands/view/PasteComponent.ts | 53 +++++++++++++++++++ 5 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 packages/core/test/specs/commands/view/PasteComponent.ts diff --git a/packages/core/src/commands/index.ts b/packages/core/src/commands/index.ts index cdb26e389..65283f130 100644 --- a/packages/core/src/commands/index.ts +++ b/packages/core/src/commands/index.ts @@ -36,12 +36,7 @@ */ import { isFunction, includes } from 'underscore'; -import CommandAbstract, { - Command, - CommandConstructor, - CommandOptions, - CommandStored, -} from './view/CommandAbstract'; +import CommandAbstract, { Command, CommandConstructor, CommandOptions, CommandStored } from './view/CommandAbstract'; import CanvasClear from './view/CanvasClear'; import CanvasMove from './view/CanvasMove'; import ComponentDelete from './view/ComponentDelete'; @@ -336,7 +331,10 @@ export default class CommandsModule extends Module(id: TId, cmd: CommandObjectById = {} as CommandObjectById) { + extend( + id: TId, + cmd: CommandObjectById = {} as CommandObjectById, + ) { const command = this.get(id); if (command) { diff --git a/packages/core/src/commands/registry.ts b/packages/core/src/commands/registry.ts index cc1091102..8b0db06a7 100644 --- a/packages/core/src/commands/registry.ts +++ b/packages/core/src/commands/registry.ts @@ -10,6 +10,7 @@ import type { FullscreenCommandRegistryRun, FullscreenCommandRegistryStop } from import type { PreviewCommandRegistryRun, PreviewCommandRegistryStop } from './view/Preview'; import type { ResizeCommandRegistryRun, ResizeCommandRegistryStop } from './view/Resize'; import type { CopyComponentCommandRegistryRun } from './view/CopyComponent'; +import type { PasteComponentCommandRegistryRun } from './view/PasteComponent'; type CommandRegistryHandler = (...args: any[]) => any; type CommandRegistryEntry = TId extends keyof TRegistry @@ -22,7 +23,8 @@ export interface CommandRegistryRun extends FullscreenCommandRegistryRun, PreviewCommandRegistryRun, ResizeCommandRegistryRun, - CopyComponentCommandRegistryRun {} + CopyComponentCommandRegistryRun, + PasteComponentCommandRegistryRun {} export interface CommandRegistryStop extends FullscreenCommandRegistryStop, diff --git a/packages/core/src/commands/view/CopyComponent.ts b/packages/core/src/commands/view/CopyComponent.ts index 5f42343fa..99d243745 100644 --- a/packages/core/src/commands/view/CopyComponent.ts +++ b/packages/core/src/commands/view/CopyComponent.ts @@ -4,7 +4,6 @@ import CommandAbstract from './CommandAbstract'; export interface CopyComponentCommandRegistryRun { 'core:copy': CommandPublicFnFromHandler; - copy: CommandPublicFnFromHandler; } export default class CommandCopyComponent extends CommandAbstract { diff --git a/packages/core/src/commands/view/PasteComponent.ts b/packages/core/src/commands/view/PasteComponent.ts index f97f926b3..2e0957272 100644 --- a/packages/core/src/commands/view/PasteComponent.ts +++ b/packages/core/src/commands/view/PasteComponent.ts @@ -1,11 +1,20 @@ -import { isArray, contains } from 'underscore'; +import { contains, isArray } from 'underscore'; import Component from '../../dom_components/model/Component'; import { ComponentsEvents } from '../../dom_components/types'; -import { CommandObject } from './CommandAbstract'; import Editor from '../../editor'; +import type { CommandPublicFnFromHandler } from '../registryHelpers'; +import CommandAbstract from './CommandAbstract'; -export default { - run(ed, s, opts = {}) { +export interface PasteComponentOptions { + action?: string; +} + +export interface PasteComponentCommandRegistryRun { + 'core:paste': CommandPublicFnFromHandler; +} + +export default class CommandPasteComponent extends CommandAbstract { + run(ed: Editor, _sender: any, opts: PasteComponentOptions = {}) { const em = ed.getModel(); const clp: Component[] | null = em.get('clipboard'); const lastSelected = ed.getSelected(); @@ -25,8 +34,6 @@ export default { added = doAdd(ed, clp, selected.parent()!, addOpts); } } else { - // Page body is selected - // Paste at the end of the body const pageBody = em.Pages.getSelected()?.getMainComponent(); const addOpts = { at: pageBody?.components().length || 0, action: opts.action || 'paste-component' }; @@ -39,8 +46,8 @@ export default { lastSelected.emitUpdate(); } - }, -} as CommandObject; + } +} function doAdd(ed: Editor, clp: Component[], parent: Component, addOpts: any): Component[] | Component { const copyable = clp.filter((cop) => cop.get('copyable')); diff --git a/packages/core/test/specs/commands/view/PasteComponent.ts b/packages/core/test/specs/commands/view/PasteComponent.ts new file mode 100644 index 000000000..ff5373d98 --- /dev/null +++ b/packages/core/test/specs/commands/view/PasteComponent.ts @@ -0,0 +1,53 @@ +import { ComponentsEvents } from '../../../../src/dom_components/types'; +import PasteComponent from '../../../../src/commands/view/PasteComponent'; + +describe('PasteComponent command', () => { + test('should paste a clone into the selected collection and emit paste event', () => { + const command = new PasteComponent({}); + const added = { id: 'added' }; + const collection = { + add: jest.fn(() => added), + }; + const selected = { + collection, + index: jest.fn(() => 2), + get: jest.fn((key: string) => (key === 'copyable' ? true : undefined)), + clone: jest.fn(() => ({ id: 'clone' })), + parent: jest.fn(), + }; + const clipboard = [selected]; + const emitUpdate = jest.fn(); + const trigger = jest.fn(); + const editor = { + getModel: jest.fn(() => ({ + get: jest.fn((key: string) => (key === 'clipboard' ? clipboard : undefined)), + })), + getSelected: jest.fn(() => ({ emitUpdate })), + getSelectedAll: jest.fn(() => [selected]), + trigger, + } as any; + + command.run(editor, null, { action: 'clone-component' }); + + expect(collection.add).toHaveBeenCalled(); + expect(trigger).toHaveBeenCalledWith(ComponentsEvents.paste, added); + expect(emitUpdate).toHaveBeenCalledTimes(1); + }); + + test('should do nothing without clipboard content or selection', () => { + const command = new PasteComponent({}); + const trigger = jest.fn(); + const editor = { + getModel: jest.fn(() => ({ + get: jest.fn(() => null), + })), + getSelected: jest.fn(() => null), + getSelectedAll: jest.fn(() => []), + trigger, + } as any; + + command.run(editor, null); + + expect(trigger).not.toHaveBeenCalled(); + }); +});