mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 17:47:27 +00:00
♻️ refactor: refactor with editor runtime
This commit is contained in:
parent
37e33b8b73
commit
be2b41c792
23 changed files with 1511 additions and 293 deletions
|
|
@ -185,6 +185,7 @@
|
|||
"@lobechat/database": "workspace:*",
|
||||
"@lobechat/desktop-bridge": "workspace:*",
|
||||
"@lobechat/edge-config": "workspace:*",
|
||||
"@lobechat/editor-runtime": "workspace:*",
|
||||
"@lobechat/electron-client-ipc": "workspace:*",
|
||||
"@lobechat/electron-server-ipc": "workspace:*",
|
||||
"@lobechat/fetch-sse": "workspace:*",
|
||||
|
|
|
|||
|
|
@ -5,19 +5,16 @@
|
|||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./client": "./src/client/index.ts",
|
||||
"./executor": "./src/executor/index.ts",
|
||||
"./executionRuntime": "./src/ExecutionRuntime/index.ts"
|
||||
"./executor": "./src/executor/index.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"dependencies": {
|
||||
"lexical": "^0.39.0"
|
||||
"@lobechat/editor-runtime": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lobechat/types": "workspace:*",
|
||||
"@lobehub/editor": "^3"
|
||||
"@lobechat/types": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@lobehub/editor": "^3",
|
||||
"@lobehub/ui": "^4",
|
||||
"antd-style": "*",
|
||||
"debug": "*",
|
||||
|
|
|
|||
|
|
@ -1,145 +0,0 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`PageAgentExecutionRuntime > modifyNodes > error handling > should normalize single operation to array 1`] = `
|
||||
"# Title
|
||||
|
||||
Single op
|
||||
|
||||
First paragraph.
|
||||
|
||||
Second paragraph.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`PageAgentExecutionRuntime > modifyNodes > insert > should insert multiple nodes after same node 1`] = `
|
||||
"# Title
|
||||
|
||||
Insert 3
|
||||
|
||||
Insert 2
|
||||
|
||||
Insert 1
|
||||
|
||||
First paragraph.
|
||||
|
||||
Second paragraph.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`PageAgentExecutionRuntime > modifyNodes > insert > should insert single node after existing node 1`] = `
|
||||
"# Title
|
||||
|
||||
New inserted paragraph
|
||||
|
||||
First paragraph.
|
||||
|
||||
Second paragraph.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`PageAgentExecutionRuntime > modifyNodes > mixed operations > should handle insert, modify, and remove in single call 1`] = `
|
||||
"# Title
|
||||
|
||||
# Updated title
|
||||
|
||||
New content
|
||||
|
||||
First paragraph.
|
||||
|
||||
Second paragraph.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`PageAgentExecutionRuntime > modifyNodes > modify > should modify existing node content 1`] = `
|
||||
"# Title
|
||||
|
||||
First paragraph.
|
||||
|
||||
Modified content here
|
||||
|
||||
Second paragraph.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`PageAgentExecutionRuntime > modifyNodes > modify > should modify multiple nodes at once 1`] = `
|
||||
"# Title
|
||||
|
||||
First paragraph.
|
||||
|
||||
Modified first
|
||||
|
||||
Second paragraph.
|
||||
|
||||
Modified second
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`PageAgentExecutionRuntime > modifyNodes > remove > should remove existing node 1`] = `
|
||||
"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<h1 id="ll63">
|
||||
<span id="lqqe">Title</span>
|
||||
</h1>
|
||||
<p id="m7fb">
|
||||
<span id="mczm">Second paragraph.</span>
|
||||
</p>
|
||||
</root>"
|
||||
`;
|
||||
|
||||
exports[`PageAgentExecutionRuntime > modifyNodes > remove > should remove multiple nodes 1`] = `
|
||||
"# Title
|
||||
|
||||
First paragraph.
|
||||
|
||||
Second paragraph.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`PageAgentExecutionRuntime > replaceText > should replace all occurrences by default 1`] = `
|
||||
"Hi world. This is a test. Hi again. Testing the world.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`PageAgentExecutionRuntime > replaceText > should support regex first occurrence only 1`] = `
|
||||
"X world. This is a test. Hello again. Testing the world.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`PageAgentExecutionRuntime > replaceText > should support regex patterns with optional groups 1`] = `
|
||||
"Hello world. This is a demo. Hello again. Testing the world.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`PageAgentExecutionRuntime > replaceText > should support regex with alternation 1`] = `
|
||||
"X X. This is a test. X again. Testing the X.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`PageAgentExecutionRuntime > replaceText > should support regex with character classes 1`] = `
|
||||
"Guest and Guest are online.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`PageAgentExecutionRuntime > replaceText > should support regex with quantifiers 1`] = `
|
||||
"Hi world! Hi again!
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`PageAgentExecutionRuntime > replaceText > should support regex with word boundaries 1`] = `
|
||||
"Hello universe. This is a test. Hello again. Testing the universe.
|
||||
|
||||
"
|
||||
`;
|
||||
|
|
@ -7,7 +7,9 @@ import { Trans, useTranslation } from 'react-i18next';
|
|||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { EditTitleArgs, EditTitleState } from '../../../types';
|
||||
import type { EditTitleArgs } from '@lobechat/editor-runtime';
|
||||
|
||||
import type { EditTitleState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
highlight: css`
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import { useTranslation } from 'react-i18next';
|
|||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { InitDocumentArgs, InitDocumentState } from '../../../types';
|
||||
import type { InitDocumentArgs } from '@lobechat/editor-runtime';
|
||||
|
||||
import type { InitDocumentState } from '../../../types';
|
||||
import { AnimatedNumber } from '../../components/AnimatedNumber';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import { useTranslation } from 'react-i18next';
|
|||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { ModifyNodesArgs, ModifyNodesState } from '../../../types';
|
||||
import type { ModifyNodesArgs } from '@lobechat/editor-runtime';
|
||||
|
||||
import type { ModifyNodesState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
insert: css`
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import { useTranslation } from 'react-i18next';
|
|||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { ReplaceTextArgs, ReplaceTextState } from '../../../types';
|
||||
import type { ReplaceTextArgs } from '@lobechat/editor-runtime';
|
||||
|
||||
import type { ReplaceTextState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
arrow: css`
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import type { EditorRuntime } from '@lobechat/editor-runtime';
|
||||
import type { BuiltinToolContext } from '@lobechat/types';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { PageAgentExecutionRuntime } from '../ExecutionRuntime';
|
||||
import { PageAgentIdentifier } from '../types';
|
||||
import { PageAgentExecutor } from './index';
|
||||
|
||||
describe('PageAgentExecutor', () => {
|
||||
let executor: PageAgentExecutor;
|
||||
let mockRuntime: PageAgentExecutionRuntime;
|
||||
let mockRuntime: EditorRuntime;
|
||||
const mockContext = {} as BuiltinToolContext;
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -18,7 +18,7 @@ describe('PageAgentExecutor', () => {
|
|||
initPage: vi.fn(),
|
||||
modifyNodes: vi.fn(),
|
||||
replaceText: vi.fn(),
|
||||
} as unknown as PageAgentExecutionRuntime;
|
||||
} as unknown as EditorRuntime;
|
||||
|
||||
executor = new PageAgentExecutor(mockRuntime);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
import { BaseExecutor, type BuiltinToolResult } from '@lobechat/types';
|
||||
|
||||
import { PageAgentExecutionRuntime } from '../ExecutionRuntime';
|
||||
import type {
|
||||
EditTitleArgs,
|
||||
EditTitleState,
|
||||
EditorRuntime,
|
||||
GetPageContentArgs,
|
||||
GetPageContentState,
|
||||
InitDocumentArgs,
|
||||
InitDocumentState,
|
||||
ModifyNodesArgs,
|
||||
ModifyNodesState,
|
||||
ReplaceTextArgs,
|
||||
} from '@lobechat/editor-runtime';
|
||||
import { BaseExecutor, type BuiltinToolResult } from '@lobechat/types';
|
||||
|
||||
import type {
|
||||
EditTitleState,
|
||||
GetPageContentState,
|
||||
InitDocumentState,
|
||||
ModifyNodesState,
|
||||
ReplaceTextState,
|
||||
} from '../types';
|
||||
import { PageAgentIdentifier } from '../types';
|
||||
|
|
@ -39,7 +41,7 @@ const PageAgentApiName = {
|
|||
/**
|
||||
* Page Agent Executor
|
||||
*
|
||||
* Wraps the PageAgentExecutionRuntime to provide a unified executor interface
|
||||
* Wraps the EditorRuntime to provide a unified executor interface
|
||||
* that follows the BaseExecutor pattern used by other builtin tools.
|
||||
*
|
||||
* Note: Page Agent is a client-side tool that directly manipulates the Lexical editor.
|
||||
|
|
@ -53,9 +55,9 @@ class PageAgentExecutor extends BaseExecutor<typeof PageAgentApiName> {
|
|||
* The execution runtime instance
|
||||
* This is a singleton that should be configured with an editor instance externally
|
||||
*/
|
||||
private runtime: PageAgentExecutionRuntime;
|
||||
private runtime: EditorRuntime;
|
||||
|
||||
constructor(runtime: PageAgentExecutionRuntime) {
|
||||
constructor(runtime: EditorRuntime) {
|
||||
super();
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,25 @@
|
|||
// Re-export runtime types from @lobechat/editor-runtime
|
||||
export type {
|
||||
EditTitleArgs,
|
||||
GetPageContentArgs,
|
||||
InitDocumentArgs,
|
||||
ModifyInsertOperation,
|
||||
ModifyNodesArgs,
|
||||
ModifyOperation,
|
||||
ModifyOperationResult,
|
||||
ModifyRemoveOperation,
|
||||
ModifyUpdateOperation,
|
||||
ReplaceTextArgs,
|
||||
} from '@lobechat/editor-runtime';
|
||||
|
||||
export { PageAgentManifest } from './manifest';
|
||||
export { systemPrompt } from './systemRole';
|
||||
export {
|
||||
DocumentApiName,
|
||||
type EditTitleArgs,
|
||||
type EditTitleState,
|
||||
type GetPageContentArgs,
|
||||
type GetPageContentState,
|
||||
type InitDocumentArgs,
|
||||
type InitDocumentState,
|
||||
type ModifyInsertOperation,
|
||||
type ModifyNodesArgs,
|
||||
type ModifyNodesState,
|
||||
type ModifyOperation,
|
||||
type ModifyOperationResult,
|
||||
type ModifyRemoveOperation,
|
||||
type ModifyUpdateOperation,
|
||||
PageAgentIdentifier,
|
||||
type ReplaceTextArgs,
|
||||
type ReplaceTextState,
|
||||
} from './types';
|
||||
|
|
|
|||
|
|
@ -22,65 +22,6 @@ export const DocumentApiName = {
|
|||
};
|
||||
/* eslint-enable sort-keys-fix/sort-keys-fix */
|
||||
|
||||
// ============ Initialize Args ============
|
||||
export interface InitDocumentArgs {
|
||||
markdown: string;
|
||||
}
|
||||
|
||||
// ============ Document Metadata Args ============
|
||||
export interface EditTitleArgs {
|
||||
title: string;
|
||||
}
|
||||
|
||||
// ============ Query & Search Args ============
|
||||
export interface GetPageContentArgs {
|
||||
format?: 'xml' | 'markdown' | 'both';
|
||||
}
|
||||
|
||||
// ============ Unified Modify Nodes Args ============
|
||||
|
||||
/** Insert operation: insert a node before or after a reference node */
|
||||
export type ModifyInsertOperation =
|
||||
| {
|
||||
action: 'insert';
|
||||
afterId: string;
|
||||
litexml: string;
|
||||
}
|
||||
| {
|
||||
action: 'insert';
|
||||
beforeId: string;
|
||||
litexml: string;
|
||||
};
|
||||
|
||||
/** Remove operation: remove a node by ID */
|
||||
export interface ModifyRemoveOperation {
|
||||
action: 'remove';
|
||||
id: string;
|
||||
}
|
||||
|
||||
/** Modify operation: update existing nodes by their IDs (embedded in litexml) */
|
||||
export interface ModifyUpdateOperation {
|
||||
action: 'modify';
|
||||
litexml: string | string[];
|
||||
}
|
||||
|
||||
/** Union type for all modify operations */
|
||||
export type ModifyOperation = ModifyInsertOperation | ModifyRemoveOperation | ModifyUpdateOperation;
|
||||
|
||||
/** Args for the unified modifyNodes API */
|
||||
export interface ModifyNodesArgs {
|
||||
operations: ModifyOperation[];
|
||||
}
|
||||
|
||||
// ============ Text Operations Args ============
|
||||
export interface ReplaceTextArgs {
|
||||
newText: string;
|
||||
nodeIds?: string[];
|
||||
replaceAll?: boolean;
|
||||
searchText: string;
|
||||
useRegex?: boolean;
|
||||
}
|
||||
|
||||
// ============ State Types for Renders ============
|
||||
|
||||
export interface GetPageContentState {
|
||||
|
|
@ -95,15 +36,12 @@ export interface GetPageContentState {
|
|||
xml?: string;
|
||||
}
|
||||
|
||||
/** Result of a single modify operation */
|
||||
export interface ModifyOperationResult {
|
||||
action: 'insert' | 'remove' | 'modify';
|
||||
error?: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface ModifyNodesState {
|
||||
results: ModifyOperationResult[];
|
||||
results: Array<{
|
||||
action: 'insert' | 'remove' | 'modify';
|
||||
error?: string;
|
||||
success: boolean;
|
||||
}>;
|
||||
successCount: number;
|
||||
totalCount: number;
|
||||
}
|
||||
|
|
@ -126,37 +64,3 @@ export interface EditTitleState {
|
|||
newTitle: string;
|
||||
previousTitle: string;
|
||||
}
|
||||
|
||||
// ============ Runtime Result Types ============
|
||||
// These are the raw result types returned by Runtime methods
|
||||
// Executor is responsible for converting these to BuiltinToolResult format
|
||||
|
||||
export interface InitPageRuntimeResult {
|
||||
extractedTitle?: string;
|
||||
nodeCount: number;
|
||||
}
|
||||
|
||||
export interface EditTitleRuntimeResult {
|
||||
newTitle: string;
|
||||
previousTitle: string;
|
||||
}
|
||||
|
||||
export interface GetPageContentRuntimeResult {
|
||||
charCount?: number;
|
||||
documentId: string;
|
||||
lineCount?: number;
|
||||
markdown?: string;
|
||||
title: string;
|
||||
xml?: string;
|
||||
}
|
||||
|
||||
export interface ModifyNodesRuntimeResult {
|
||||
results: ModifyOperationResult[];
|
||||
successCount: number;
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
export interface ReplaceTextRuntimeResult {
|
||||
modifiedNodeIds: string[];
|
||||
replacementCount: number;
|
||||
}
|
||||
|
|
|
|||
20
packages/editor-runtime/package.json
Normal file
20
packages/editor-runtime/package.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "@lobechat/editor-runtime",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"dependencies": {
|
||||
"@lobechat/prompts": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lobehub/editor": "^3",
|
||||
"lexical": "^0.39.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@lobehub/editor": "^3",
|
||||
"debug": "*"
|
||||
}
|
||||
}
|
||||
|
|
@ -14,17 +14,20 @@ import type {
|
|||
ModifyOperationResult,
|
||||
ReplaceTextArgs,
|
||||
ReplaceTextRuntimeResult,
|
||||
} from '../types';
|
||||
} from './types';
|
||||
|
||||
const log = debug('page:page-agent');
|
||||
const log = debug('lobe:editor-runtime');
|
||||
|
||||
/**
|
||||
* Page Agent Execution Runtime
|
||||
* Handles the execution logic for all Page Agent (Document) APIs
|
||||
*
|
||||
* See `packages/builtin-agents/src/agents/page-agent/README.md` for more detailsd
|
||||
* Editor Execution Runtime
|
||||
* Handles the execution logic for editor operations including:
|
||||
* - Document initialization
|
||||
* - Title management
|
||||
* - Content retrieval
|
||||
* - Node modifications (insert, modify, remove)
|
||||
* - Text replacement
|
||||
*/
|
||||
export class PageAgentExecutionRuntime {
|
||||
export class EditorRuntime {
|
||||
private editor: IEditor | null = null;
|
||||
private titleSetter: ((title: string) => void) | null = null;
|
||||
private titleGetter: (() => string) | null = null;
|
||||
182
packages/editor-runtime/src/__tests__/EditorRuntime.real.test.ts
Normal file
182
packages/editor-runtime/src/__tests__/EditorRuntime.real.test.ts
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
import {
|
||||
CommonPlugin,
|
||||
type IEditor,
|
||||
Kernel,
|
||||
LitexmlPlugin,
|
||||
MarkdownPlugin,
|
||||
moment,
|
||||
} from '@lobehub/editor';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { EditorRuntime } from '../EditorRuntime';
|
||||
import editAllFixture from './fixtures/edit-all.json';
|
||||
import removeFixture from './fixtures/remove.json';
|
||||
|
||||
describe('EditorRuntime - Real Cases', () => {
|
||||
let runtime: EditorRuntime;
|
||||
let editor: IEditor;
|
||||
let mockTitleSetter: ReturnType<typeof vi.fn>;
|
||||
let mockTitleGetter: ReturnType<typeof vi.fn>;
|
||||
|
||||
beforeEach(() => {
|
||||
editor = new Kernel();
|
||||
editor.registerPlugins([CommonPlugin, MarkdownPlugin, LitexmlPlugin]);
|
||||
editor.initNodeEditor();
|
||||
|
||||
runtime = new EditorRuntime();
|
||||
runtime.setEditor(editor);
|
||||
|
||||
mockTitleSetter = vi.fn();
|
||||
mockTitleGetter = vi.fn().mockReturnValue('Test Title');
|
||||
runtime.setTitleHandlers(mockTitleSetter, mockTitleGetter);
|
||||
});
|
||||
|
||||
describe('modifyNodes - batch modify all paragraphs', () => {
|
||||
it('should modify all 16 paragraphs in a single call', async () => {
|
||||
// Initialize editor with the JSON fixture
|
||||
editor.setDocument('json', editAllFixture);
|
||||
await moment();
|
||||
|
||||
// Get the XML to verify initial state
|
||||
const xmlBefore = editor.getDocument('litexml') as unknown as string;
|
||||
const paragraphMatches = [...xmlBefore.matchAll(/<p id="([^"]+)"/g)];
|
||||
expect(paragraphMatches.length).toBe(16);
|
||||
|
||||
// Extract paragraph IDs from the XML
|
||||
const paragraphIds = paragraphMatches.map((m) => m[1]);
|
||||
|
||||
const result = await runtime.modifyNodes({
|
||||
operations: [
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[0]}">(雨点敲打着咖啡馆的玻璃窗,像无数细小的手指在弹奏着无声的钢琴。林晓坐在靠窗的位置,手中的咖啡已经凉了,她却浑然不觉。这是她第三次来到这家咖啡馆,每次都是同样的位置,同样的时间。)</p>`,
|
||||
},
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[1]}">(窗外雨丝如帘,街灯昏黄。咖啡馆内灯光柔和,墙上挂着旧照片,书架上摆满了书。空气里是咖啡香和旧书纸的味道。)</p>`,
|
||||
},
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[2]}">林晓:(内心独白)第一次来的时候,也是这样的雨夜。那天我刚结束一段五年的感情,整个人像是被掏空了。我点了一杯美式咖啡,就这样坐着,看着窗外的雨,直到打烊。</p>`,
|
||||
},
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[3]}">林晓:(继续独白)第二次来的时候,我遇到了他。穿着灰色风衣,坐在对面的位置。他一直在看书,偶尔抬头看看窗外。他的手指修长,翻书的动作优雅从容。</p>`,
|
||||
},
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[4]}">林晓:(独白)今天,我又来了。雨还是那样下着,咖啡馆还是那样安静。我不知道自己在期待什么,也许只是习惯了这种孤独的仪式感。</p>`,
|
||||
},
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[5]}">(门上的风铃响了,有人推门进来。林晓下意识地抬头,心跳突然漏了一拍。)</p>`,
|
||||
},
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[6]}">(风铃叮咚作响。一个身影站在门口,雨伞滴着水,灯光勾勒出他的轮廓。)</p>`,
|
||||
},
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[7]}">林晓:(低声)是他。</p>`,
|
||||
},
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[8]}">(他收起雨伞,抖了抖身上的水珠,然后径直走向她。这一次,他没有坐在对面的位置,而是在她面前停了下来。)</p>`,
|
||||
},
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[9]}">陈默:(声音低沉温和)我可以坐这里吗?</p>`,
|
||||
},
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[10]}">(林晓点了点头,喉咙有些发干。窗外的雨声似乎变小了,咖啡馆里的音乐也变得清晰起来。)</p>`,
|
||||
},
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[11]}">陈默:(眼中带着笑意)我注意到你每次都在这里。</p>`,
|
||||
},
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[12]}">林晓:(凝视着他,内心独白)他的眼睛是深褐色的,像秋天的落叶,温暖而深邃。他的鼻梁挺直,唇线分明,微笑时眼角有细微的皱纹,更添了几分沧桑感。我忽然觉得这个人似曾相识,却又分明是第一次见面。</p>`,
|
||||
},
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[13]}">陈默:(伸出手)我叫陈默。很高兴终于能和你说话。我观察你三次了,每次你都这样静静地坐着看雨,若有所思,若有所待。</p>`,
|
||||
},
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[14]}">(林晓握住他的手,感受到他掌心的温度。他的手温暖,却有薄茧,像是经常写字或弹琴的人。雨还在下,但咖啡馆里忽然觉得温暖如春,先前的孤独寂寥,竟然悄然消散了。)</p>`,
|
||||
},
|
||||
{
|
||||
action: 'modify',
|
||||
litexml: `<p id="${paragraphIds[15]}">(旁白)也许,有些相遇注定要在雨夜发生。就像有些故事,注定要从一句简单的问候开始。</p>`,
|
||||
},
|
||||
],
|
||||
});
|
||||
await moment();
|
||||
|
||||
// Verify all operations succeeded
|
||||
expect(result.successCount).toBe(16);
|
||||
expect(result.totalCount).toBe(16);
|
||||
expect(result.results.every((r) => r.success)).toBe(true);
|
||||
expect(result.results.every((r) => r.action === 'modify')).toBe(true);
|
||||
|
||||
// Verify the content was modified
|
||||
const markdown = editor.getDocument('markdown') as unknown as string;
|
||||
expect(markdown).toContain('雨点敲打着咖啡馆的玻璃窗');
|
||||
expect(markdown).toContain('林晓:(低声)是他。');
|
||||
expect(markdown).toContain('陈默:(声音低沉温和)我可以坐这里吗?');
|
||||
expect(markdown).toContain('也许,有些相遇注定要在雨夜发生');
|
||||
expect(markdown).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('modifyNodes - batch remove paragraphs', () => {
|
||||
it('should remove 7 paragraphs in a single call', async () => {
|
||||
// Initialize editor with the JSON fixture
|
||||
editor.setDocument('json', removeFixture);
|
||||
await moment();
|
||||
|
||||
// Get paragraph count before removal
|
||||
const xmlBefore = editor.getDocument('litexml') as unknown as string;
|
||||
const paragraphsBefore = [...xmlBefore.matchAll(/<p id="([^"]+)"/g)];
|
||||
const initialCount = paragraphsBefore.length;
|
||||
|
||||
const result = await runtime.modifyNodes({
|
||||
operations: [
|
||||
{ action: 'remove', id: 'wps3' },
|
||||
{ action: 'remove', id: 'w936' },
|
||||
{ action: 'remove', id: 'vse9' },
|
||||
{ action: 'remove', id: 'sp45' },
|
||||
{ action: 'remove', id: 's8f8' },
|
||||
{ action: 'remove', id: 'rrqb' },
|
||||
{ action: 'remove', id: 'plu1' },
|
||||
],
|
||||
});
|
||||
await moment();
|
||||
|
||||
// Verify all operations succeeded
|
||||
expect(result.successCount).toBe(7);
|
||||
expect(result.totalCount).toBe(7);
|
||||
expect(result.results.every((r) => r.success)).toBe(true);
|
||||
expect(result.results.every((r) => r.action === 'remove')).toBe(true);
|
||||
|
||||
// Verify paragraphs were removed
|
||||
const xmlAfter = editor.getDocument('litexml') as unknown as string;
|
||||
const paragraphsAfter = [...xmlAfter.matchAll(/<p id="([^"]+)"/g)];
|
||||
|
||||
expect(paragraphsAfter.length).toBe(initialCount - 7);
|
||||
|
||||
// Verify the removed IDs are no longer present
|
||||
expect(xmlAfter).not.toContain('id="wps3"');
|
||||
expect(xmlAfter).not.toContain('id="w936"');
|
||||
expect(xmlAfter).not.toContain('id="vse9"');
|
||||
expect(xmlAfter).not.toContain('id="sp45"');
|
||||
expect(xmlAfter).not.toContain('id="s8f8"');
|
||||
expect(xmlAfter).not.toContain('id="rrqb"');
|
||||
expect(xmlAfter).not.toContain('id="plu1"');
|
||||
|
||||
expect(xmlAfter).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -9,10 +9,10 @@ import {
|
|||
import { resetRandomKey } from 'lexical';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { PageAgentExecutionRuntime } from './index';
|
||||
import { EditorRuntime } from '../EditorRuntime';
|
||||
|
||||
describe('PageAgentExecutionRuntime', () => {
|
||||
let runtime: PageAgentExecutionRuntime;
|
||||
describe('EditorRuntime', () => {
|
||||
let runtime: EditorRuntime;
|
||||
let editor: IEditor;
|
||||
let mockTitleSetter: ReturnType<typeof vi.fn>;
|
||||
let mockTitleGetter: ReturnType<typeof vi.fn>;
|
||||
|
|
@ -23,7 +23,7 @@ describe('PageAgentExecutionRuntime', () => {
|
|||
editor.registerPlugins([CommonPlugin, MarkdownPlugin, LitexmlPlugin]);
|
||||
editor.initNodeEditor();
|
||||
|
||||
runtime = new PageAgentExecutionRuntime();
|
||||
runtime = new EditorRuntime();
|
||||
runtime.setEditor(editor);
|
||||
|
||||
mockTitleSetter = vi.fn();
|
||||
|
|
@ -515,7 +515,7 @@ describe('PageAgentExecutionRuntime', () => {
|
|||
});
|
||||
|
||||
it('should return undefined when no document ID is set', () => {
|
||||
const newRuntime = new PageAgentExecutionRuntime();
|
||||
const newRuntime = new EditorRuntime();
|
||||
expect(newRuntime.getCurrentDocId()).toBeUndefined();
|
||||
});
|
||||
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`EditorRuntime - Real Cases > modifyNodes - batch modify all paragraphs > should modify all 16 paragraphs in a single call 1`] = `
|
||||
"(雨点敲打着咖啡馆的玻璃窗,像无数细小的手指在弹奏着无声的钢琴。林晓坐在靠窗的位置,手中的咖啡已经凉了,她却浑然不觉。这是她第三次来到这家咖啡馆,每次都是同样的位置,同样的时间。)
|
||||
|
||||
(窗外雨丝如帘,街灯昏黄。咖啡馆内灯光柔和,墙上挂着旧照片,书架上摆满了书。空气里是咖啡香和旧书纸的味道。)
|
||||
|
||||
林晓:(内心独白)第一次来的时候,也是这样的雨夜。那天我刚结束一段五年的感情,整个人像是被掏空了。我点了一杯美式咖啡,就这样坐着,看着窗外的雨,直到打烊。
|
||||
|
||||
林晓:(继续独白)第二次来的时候,我遇到了他。穿着灰色风衣,坐在对面的位置。他一直在看书,偶尔抬头看看窗外。他的手指修长,翻书的动作优雅从容。
|
||||
|
||||
林晓:(独白)今天,我又来了。雨还是那样下着,咖啡馆还是那样安静。我不知道自己在期待什么,也许只是习惯了这种孤独的仪式感。
|
||||
|
||||
(门上的风铃响了,有人推门进来。林晓下意识地抬头,心跳突然漏了一拍。)
|
||||
|
||||
(风铃叮咚作响。一个身影站在门口,雨伞滴着水,灯光勾勒出他的轮廓。)
|
||||
|
||||
林晓:(低声)是他。
|
||||
|
||||
(他收起雨伞,抖了抖身上的水珠,然后径直走向她。这一次,他没有坐在对面的位置,而是在她面前停了下来。)
|
||||
|
||||
陈默:(声音低沉温和)我可以坐这里吗?
|
||||
|
||||
(林晓点了点头,喉咙有些发干。窗外的雨声似乎变小了,咖啡馆里的音乐也变得清晰起来。)
|
||||
|
||||
陈默:(眼中带着笑意)我注意到你每次都在这里。
|
||||
|
||||
林晓:(凝视着他,内心独白)他的眼睛是深褐色的,像秋天的落叶,温暖而深邃。他的鼻梁挺直,唇线分明,微笑时眼角有细微的皱纹,更添了几分沧桑感。我忽然觉得这个人似曾相识,却又分明是第一次见面。
|
||||
|
||||
陈默:(伸出手)我叫陈默。很高兴终于能和你说话。我观察你三次了,每次你都这样静静地坐着看雨,若有所思,若有所待。
|
||||
|
||||
(林晓握住他的手,感受到他掌心的温度。他的手温暖,却有薄茧,像是经常写字或弹琴的人。雨还在下,但咖啡馆里忽然觉得温暖如春,先前的孤独寂寥,竟然悄然消散了。)
|
||||
|
||||
(旁白)也许,有些相遇注定要在雨夜发生。就像有些故事,注定要从一句简单的问候开始。
|
||||
|
||||
"
|
||||
`;
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`EditorRuntime > modifyNodes > error handling > should normalize single operation to array 1`] = `
|
||||
"# Title
|
||||
|
||||
Single op
|
||||
|
||||
First paragraph.
|
||||
|
||||
Second paragraph.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`EditorRuntime > modifyNodes > insert > should insert multiple nodes after same node 1`] = `
|
||||
"# Title
|
||||
|
||||
Insert 1
|
||||
|
||||
Insert 2
|
||||
|
||||
Insert 3
|
||||
|
||||
First paragraph.
|
||||
|
||||
Second paragraph.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`EditorRuntime > modifyNodes > insert > should insert single node after existing node 1`] = `
|
||||
"# Title
|
||||
|
||||
New inserted paragraph
|
||||
|
||||
First paragraph.
|
||||
|
||||
Second paragraph.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`EditorRuntime > modifyNodes > mixed operations > should handle insert, modify, and remove in single call 1`] = `
|
||||
"# Updated title
|
||||
|
||||
New content
|
||||
|
||||
First paragraph.
|
||||
|
||||
Second paragraph.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`EditorRuntime > modifyNodes > modify > should modify existing node content 1`] = `
|
||||
"# Title
|
||||
|
||||
Modified content here
|
||||
|
||||
Second paragraph.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`EditorRuntime > modifyNodes > modify > should modify multiple nodes at once 1`] = `
|
||||
"# Title
|
||||
|
||||
Modified first
|
||||
|
||||
Modified second
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`EditorRuntime > modifyNodes > remove > should remove existing node 1`] = `
|
||||
"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<h1 id="ll63">
|
||||
<span id="lqqe">Title</span>
|
||||
</h1>
|
||||
<p id="m7fb">
|
||||
<span id="mczm">Second paragraph.</span>
|
||||
</p>
|
||||
</root>"
|
||||
`;
|
||||
|
||||
exports[`EditorRuntime > modifyNodes > remove > should remove multiple nodes 1`] = `
|
||||
"# Title
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`EditorRuntime > replaceText > should replace all occurrences by default 1`] = `
|
||||
"Hi world. This is a test. Hi again. Testing the world.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`EditorRuntime > replaceText > should support regex first occurrence only 1`] = `
|
||||
"X world. This is a test. Hello again. Testing the world.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`EditorRuntime > replaceText > should support regex patterns with optional groups 1`] = `
|
||||
"Hello world. This is a demo. Hello again. Testing the world.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`EditorRuntime > replaceText > should support regex with alternation 1`] = `
|
||||
"X X. This is a test. X again. Testing the X.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`EditorRuntime > replaceText > should support regex with character classes 1`] = `
|
||||
"Guest and Guest are online.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`EditorRuntime > replaceText > should support regex with quantifiers 1`] = `
|
||||
"Hi world! Hi again!
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`EditorRuntime > replaceText > should support regex with word boundaries 1`] = `
|
||||
"Hello universe. This is a test. Hello again. Testing the universe.
|
||||
|
||||
"
|
||||
`;
|
||||
364
packages/editor-runtime/src/__tests__/fixtures/edit-all.json
Normal file
364
packages/editor-runtime/src/__tests__/fixtures/edit-all.json
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
{
|
||||
"root": {
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "雨点敲打咖啡馆之玻璃窗,若无数纤指弹奏无声之琴。林晓坐于窗边,手中咖啡已凉,而浑然不觉。此乃其第三次至此咖啡馆,每回皆同一位置,同一时辰。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "183"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "182"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "窗外雨丝如帘,街灯昏黄,映照雨滴如珠帘垂落。咖啡馆内灯光柔和,墙上挂旧时照片,书架列满古籍,空气中咖啡香与旧书纸香交织。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "138"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "137"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "忆其初至之时,亦如是雨夜。彼日方了结五载之情,身心若被掏空。馆中奏轻柔爵士之乐,空气中弥漫咖啡豆之香与雨水之湿气。彼点美式咖啡一杯,遂静坐观窗外之雨,直至打烊。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "178"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "177"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "二次来时,遇彼男子。身着灰色风衣,身形颀长,坐于对面之位。其面如冠玉,眉目清朗,虽不言不语,自有儒雅之气。二人之间虽隔一桌,却似隔整个宇宙。彼未视林晓,唯专注观手中之书,偶抬头望窗外。林晓见其手指修长如玉,翻书之态优雅从容,颇有君子之风。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "130"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "129"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "今日,彼复至。雨仍如是下,咖啡馆依旧寂静。林晓不知己所期待者何,或已习惯此孤独之仪式。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "173"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "172"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "门上风铃响,有人推门而入。林晓下意识抬头,心跳忽漏一拍。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "168"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "167"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "风铃叮咚,如清泉击石。林晓抬眼望去,见一身影立于门口,雨伞滴水成线,灯光勾勒其轮廓,若水墨画中走出之人。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "135"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "134"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "是他。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "24"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "23"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "彼收雨伞,抖身上水珠,径走向林晓。此番,未坐对面之位,而于其面前止步。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "163"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "162"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "“吾可坐此处乎?”其声低沉温和,如山涧清泉。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "158"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "157"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "林晓点头,喉中微干。窗外雨声似渐小,馆中音乐愈显清晰。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "153"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "152"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "“吾观汝每回皆在此处,”彼言,眼中含笑,“吾亦然。”",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "148"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "147"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "林晓不知何言,唯凝视之。彼目深褐色,若秋日落叶,温暖深邃,似藏无尽故事。其鼻梁挺直,唇线分明,微笑时眼角微纹,更添几分沧桑韵味。林晓忽觉此人似曾相识,却又分明初见。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "125"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "124"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "“吾名陈默,”彼伸手,指节分明,掌心温暖,“甚悦终能与汝言。观汝三回,每回皆静坐观雨,若有所思,若有所待。”",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "120"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "119"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "林晓握其手,感掌心之温,心中忽生异样。彼手虽温,却有薄茧,似是常执笔或抚琴之人。雨仍下,然咖啡馆中忽觉温暖如春,先前之孤独寂寥,竟悄然消散。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "115"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "114"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "或曰,有些相遇注定于雨夜发生。如有些故事,注定始于一句简单问候。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "143"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "142"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "root",
|
||||
"version": 1,
|
||||
"id": "root"
|
||||
}
|
||||
}
|
||||
591
packages/editor-runtime/src/__tests__/fixtures/remove.json
Normal file
591
packages/editor-runtime/src/__tests__/fixtures/remove.json
Normal file
|
|
@ -0,0 +1,591 @@
|
|||
{
|
||||
"root": {
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "杭州,一座被诗意浸润的千年古都,静静地依偎在钱塘江畔,宛如一幅徐徐展开的水墨长卷。这里不仅是浙江省的政治、经济、文化中心,更是中国七大古都之一,承载着2200余年的历史记忆。从南宋临安的繁华盛景到今日数字经济的创新高地,杭州始终以\"人间天堂\"的美誉,向世界展示着东方文明的独特魅力。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "4"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "3"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "地理与气候",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "6"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "heading",
|
||||
"version": 1,
|
||||
"tag": "h2",
|
||||
"id": "5"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "杭州地处钱塘江下游,京杭大运河南端,东临杭州湾,西接天目山。全市总面积16850平方公里,常住人口超过1200万。杭州属于亚热带季风气候,四季分明,雨量充沛,年平均气温17.8℃,气候宜人。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "8"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "7"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "历史文化",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "10"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "heading",
|
||||
"version": 1,
|
||||
"tag": "h2",
|
||||
"id": "9"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "杭州是吴越文化和南宋文化的发源地之一。公元1138年,南宋定都临安(今杭州),使其成为当时世界上最繁华的城市之一。杭州拥有丰富的历史文化遗产,包括西湖文化景观、京杭大运河、良渚古城遗址等世界文化遗产。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "12"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "11"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "杭州的历史可以追溯到新石器时代的良渚文化(约公元前3300-前2300年),良渚古城遗址的发现证明了这里是中国早期文明的重要发源地之一。良渚文化以精美的玉器、发达的水利系统和复杂的社会结构著称,2019年被列入世界文化遗产名录。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "74"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "73"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "春秋战国时期,杭州属于吴越之地。公元589年,隋文帝设杭州,取\"余杭\"之名,寓意\"禹航\",相传大禹治水时曾在此停航。隋炀帝开凿京杭大运河后,杭州成为南北交通枢纽,经济文化迅速发展。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "71"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "70"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "五代十国时期,吴越国(907-978年)定都杭州,钱镠王实施保境安民政策,兴修水利,扩建城池,奠定了杭州\"东南形胜,三吴都会\"的基础。吴越国时期佛教兴盛,灵隐寺、净慈寺等名刹相继建成。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "68"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "67"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "南宋时期(1127-1279年)是杭州历史上的黄金时代。宋室南渡后定都临安(今杭州),使其成为当时世界上人口最多、经济最繁荣的城市之一。马可·波罗在游记中称杭州为\"世界上最美丽华贵之天城\"。南宋时期杭州的工商业、文化艺术、科学技术都达到了空前的高度:",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "65"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "64"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 1,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "经济繁荣",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "52"
|
||||
},
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": ":丝绸、瓷器、茶叶贸易发达,出现了世界上最早的纸币\"交子\"",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "53"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "listitem",
|
||||
"version": 1,
|
||||
"value": 1,
|
||||
"id": "51"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 1,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "文化鼎盛",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "55"
|
||||
},
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": ":宋词达到艺术高峰,苏轼、柳永、李清照等文人雅士云集",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "56"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "listitem",
|
||||
"version": 1,
|
||||
"value": 2,
|
||||
"id": "54"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 1,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "科技创新",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "58"
|
||||
},
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": ":活字印刷术、指南针、火药等重大发明得到广泛应用",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "59"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "listitem",
|
||||
"version": 1,
|
||||
"value": 3,
|
||||
"id": "57"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 1,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "城市建设",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "61"
|
||||
},
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": ":形成了\"前朝后市\"的格局,御街、清河坊等商业区繁华异常",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "62"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "listitem",
|
||||
"version": 1,
|
||||
"value": 4,
|
||||
"id": "60"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "list",
|
||||
"version": 1,
|
||||
"listType": "bullet",
|
||||
"start": 1,
|
||||
"tag": "ul",
|
||||
"id": "50"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "元明清时期,杭州虽不再是都城,但仍是江南重要的经济文化中心。明代杭州的丝绸业更加发达,\"杭纺\"名扬天下;清代康乾盛世期间,康熙、乾隆皇帝多次南巡驻跸杭州,题诗作画,进一步提升了西湖的文化地位。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "48"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "47"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "近代以来,杭州在中国现代化进程中扮演着重要角色。1861年太平天国战争后,杭州开始近代化建设;民国时期成为浙江省会;改革开放后,杭州依托西湖美景和历史文化底蕴,发展成为国际知名的旅游城市和创新创业中心。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "45"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "44"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "杭州的文化遗产不仅体现在物质层面,更融入了城市的精神气质:",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "42"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "41"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 1,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "茶文化",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "29"
|
||||
},
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": ":龙井茶被誉为\"中国十大名茶\"之首,茶道、茶艺成为杭州生活美学的重要组成部分",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "30"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "listitem",
|
||||
"version": 1,
|
||||
"value": 1,
|
||||
"id": "28"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 1,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "丝绸文化",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "32"
|
||||
},
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": ":杭州素有\"丝绸之府\"美誉,丝绸制作技艺被列入国家级非物质文化遗产",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "33"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "listitem",
|
||||
"version": 1,
|
||||
"value": 2,
|
||||
"id": "31"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 1,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "诗词文化",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "35"
|
||||
},
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": ":白居易、苏轼、林逋等文人留下的诗词歌赋,使杭州成为\"诗画江南\"的典范",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "36"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "listitem",
|
||||
"version": 1,
|
||||
"value": 3,
|
||||
"id": "34"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 1,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "佛教文化",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "38"
|
||||
},
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": ":灵隐寺、净慈寺、天竺三寺等佛教圣地,见证了杭州千年佛教传承",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "39"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "listitem",
|
||||
"version": 1,
|
||||
"value": 4,
|
||||
"id": "37"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "list",
|
||||
"version": 1,
|
||||
"listType": "bullet",
|
||||
"start": 1,
|
||||
"tag": "ul",
|
||||
"id": "27"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "西湖风光",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "14"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "heading",
|
||||
"version": 1,
|
||||
"tag": "h2",
|
||||
"id": "13"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"detail": 0,
|
||||
"format": 0,
|
||||
"mode": "normal",
|
||||
"style": "",
|
||||
"text": "西湖是杭州的灵魂,也是中国最著名的风景名胜之一。西湖十景(如苏堤春晓、断桥残雪、雷峰夕照等)闻名遐迩。西湖不仅自然风光秀丽,更承载着深厚的文化内涵,历代文人墨客在此留下了无数诗词歌赋。",
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"id": "16"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"textFormat": 0,
|
||||
"textStyle": "",
|
||||
"id": "15"
|
||||
}
|
||||
],
|
||||
"direction": "ltr",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"type": "root",
|
||||
"version": 1,
|
||||
"id": "root"
|
||||
}
|
||||
}
|
||||
2
packages/editor-runtime/src/index.ts
Normal file
2
packages/editor-runtime/src/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { EditorRuntime } from './EditorRuntime';
|
||||
export * from './types';
|
||||
97
packages/editor-runtime/src/types.ts
Normal file
97
packages/editor-runtime/src/types.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// ============ Initialize Args ============
|
||||
export interface InitDocumentArgs {
|
||||
markdown: string;
|
||||
}
|
||||
|
||||
// ============ Document Metadata Args ============
|
||||
export interface EditTitleArgs {
|
||||
title: string;
|
||||
}
|
||||
|
||||
// ============ Query & Search Args ============
|
||||
export interface GetPageContentArgs {
|
||||
format?: 'xml' | 'markdown' | 'both';
|
||||
}
|
||||
|
||||
// ============ Unified Modify Nodes Args ============
|
||||
|
||||
/** Insert operation: insert a node before or after a reference node */
|
||||
export type ModifyInsertOperation =
|
||||
| {
|
||||
action: 'insert';
|
||||
afterId: string;
|
||||
litexml: string;
|
||||
}
|
||||
| {
|
||||
action: 'insert';
|
||||
beforeId: string;
|
||||
litexml: string;
|
||||
};
|
||||
|
||||
/** Remove operation: remove a node by ID */
|
||||
export interface ModifyRemoveOperation {
|
||||
action: 'remove';
|
||||
id: string;
|
||||
}
|
||||
|
||||
/** Modify operation: update existing nodes by their IDs (embedded in litexml) */
|
||||
export interface ModifyUpdateOperation {
|
||||
action: 'modify';
|
||||
litexml: string | string[];
|
||||
}
|
||||
|
||||
/** Union type for all modify operations */
|
||||
export type ModifyOperation = ModifyInsertOperation | ModifyRemoveOperation | ModifyUpdateOperation;
|
||||
|
||||
/** Args for the unified modifyNodes API */
|
||||
export interface ModifyNodesArgs {
|
||||
operations: ModifyOperation[];
|
||||
}
|
||||
|
||||
// ============ Text Operations Args ============
|
||||
export interface ReplaceTextArgs {
|
||||
newText: string;
|
||||
nodeIds?: string[];
|
||||
replaceAll?: boolean;
|
||||
searchText: string;
|
||||
useRegex?: boolean;
|
||||
}
|
||||
|
||||
// ============ Runtime Result Types ============
|
||||
|
||||
/** Result of a single modify operation */
|
||||
export interface ModifyOperationResult {
|
||||
action: 'insert' | 'remove' | 'modify';
|
||||
error?: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface InitPageRuntimeResult {
|
||||
extractedTitle?: string;
|
||||
nodeCount: number;
|
||||
}
|
||||
|
||||
export interface EditTitleRuntimeResult {
|
||||
newTitle: string;
|
||||
previousTitle: string;
|
||||
}
|
||||
|
||||
export interface GetPageContentRuntimeResult {
|
||||
charCount?: number;
|
||||
documentId: string;
|
||||
lineCount?: number;
|
||||
markdown?: string;
|
||||
title: string;
|
||||
xml?: string;
|
||||
}
|
||||
|
||||
export interface ModifyNodesRuntimeResult {
|
||||
results: ModifyOperationResult[];
|
||||
successCount: number;
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
export interface ReplaceTextRuntimeResult {
|
||||
modifiedNodeIds: string[];
|
||||
replacementCount: number;
|
||||
}
|
||||
18
packages/editor-runtime/vitest.config.mts
Normal file
18
packages/editor-runtime/vitest.config.mts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'json', 'lcov', 'text-summary'],
|
||||
},
|
||||
environment: 'happy-dom',
|
||||
globals: true,
|
||||
server: {
|
||||
deps: {
|
||||
// Inline @emoji-mart packages to avoid ESM JSON import issues
|
||||
inline: [/@emoji-mart/, /@lobehub\/ui/],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -4,11 +4,11 @@
|
|||
* Creates and exports the PageAgentExecutor instance for registration.
|
||||
* Also exports the runtime for editor instance injection.
|
||||
*/
|
||||
import { PageAgentExecutionRuntime } from '@lobechat/builtin-tool-page-agent/executionRuntime';
|
||||
import { EditorRuntime } from '@lobechat/editor-runtime';
|
||||
import { PageAgentExecutor } from '@lobechat/builtin-tool-page-agent/executor';
|
||||
|
||||
// Create singleton instance of the runtime
|
||||
export const pageAgentRuntime = new PageAgentExecutionRuntime();
|
||||
export const pageAgentRuntime = new EditorRuntime();
|
||||
|
||||
// Create executor instance with the runtime
|
||||
export const pageAgentExecutor = new PageAgentExecutor(pageAgentRuntime);
|
||||
|
|
|
|||
Loading…
Reference in a new issue