Skip to content

Commit

Permalink
新增:MixEditor 的输入功能,部分删除功能;修复:双击聚焦节点后,输入法触发失效。
Browse files Browse the repository at this point in the history
  • Loading branch information
SuiltaPico committed Nov 14, 2024
1 parent 7a93b30 commit eac61aa
Show file tree
Hide file tree
Showing 17 changed files with 473 additions and 102 deletions.
34 changes: 34 additions & 0 deletions src/components/base/mix_editor/event/Combine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { MaybePromise } from "@/common/async";
import { BaseEvent } from "../event";
import { Area } from "../Area";

export type CombineEventResult =
| {
type: "done";
/** 合并的结束位置。 */
to: number;
}
| {
type: "skip";
};

export const CombineEventResult = {
/** 不接受合并。 */
skip: { type: "skip" } satisfies CombineEventResult,
/** 接受合并,并把光标移动到指定位置。 */
done: (to: number) => ({ type: "done", to } satisfies CombineEventResult),
};

/** 合并事件。 */
export interface CombineEvent extends BaseEvent {
event_type: "combine";
/** 要合并的区域。 */
area: Area;
/** 合并的结束位置。 */
to: number;
}

export type CombineEventPair = {
event: CombineEvent;
result: MaybePromise<CombineEventResult>;
};
67 changes: 67 additions & 0 deletions src/components/base/mix_editor/event/Delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { MaybePromise } from "@/common/async";
import { BaseEvent } from "../event";
import { Area } from "../Area";

export type DeleteEventResult =
| {
type: "done";
/** 输入的结束位置。 */
to: number;
}
| {
type: "skip";
}
| {
type: "enter_child";
/** 进入的子区域索引。 */
to: number;
}
| {
type: "self_delete_required";
};

export const DeleteEventResult = {
/** 删除成功,结束删除流程。 */
done: (to: number) => ({ type: "done", to } satisfies DeleteEventResult),
/** 跳过当前节点,让父节点处理。 */
skip: { type: "skip" } satisfies DeleteEventResult,
/** 进入子区域,让子区域处理。 */
enter_child: (to: number) =>
({
type: "enter_child",
to,
} satisfies DeleteEventResult),
/** 需要父节点删除自己。 */
self_delete_required: {
type: "self_delete_required",
} satisfies DeleteEventResult,
};

/** 定向删除事件。通常由键盘的退格键和删除键触发。 */
export interface DirectionalDeleteEvent extends BaseEvent {
event_type: "delete";
/** 删除的方向。 */
type: "forward" | "backward";
/** 删除的位置。 */
to: number;
/** 是否从子区域跳入。
*
* 如果是,父元素可能要处理好子元素的邻接问题,例如:
* - 根节点需要让当前锻炼和上一个段落邻接。 */
from_child: boolean;
}

/** 指定删除事件。指定删除范围,约定为是左闭右开。 */
export interface SpecifiedDeleteEvent extends BaseEvent {
event_type: "delete";
type: "specified";
from: number;
to: number;
}

export type DeleteEvent = DirectionalDeleteEvent | SpecifiedDeleteEvent;

export type DeleteEventPair = {
event: DeleteEvent;
result: MaybePromise<DeleteEventResult>;
};
35 changes: 35 additions & 0 deletions src/components/base/mix_editor/event/Input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { MaybePromise } from "@/common/async";
import { BaseEvent } from "../event";

export type InputEventResult =
| {
type: "done";
/** 输入的结束位置。 */
to: number;
}
| {
type: "skip";
};

export const InputEventResult = {
/** 不接受输入,跳过当前节点。 */
skip: { type: "skip" } satisfies InputEventResult,
/** 接受输入,并把光标移动到指定位置。 */
done: (to: number) => ({ type: "done", to } satisfies InputEventResult),
};

/** 输入事件。 */
export interface InputEvent extends BaseEvent {
event_type: "input";
/** 输入的值。 */
value: string;
/** 输入的位置。 */
to: number;
/** 输入的剪切板数据。 */
dataTransfer?: DataTransfer;
}

export type InputEventPair = {
event: InputEvent;
result: MaybePromise<InputEventResult>;
};
9 changes: 8 additions & 1 deletion src/components/base/mix_editor/event/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { CaretMoveEnterEventPair } from "./CaretMoveEnter";
import { CombineEventPair } from "./Combine";
import { DeleteEventPair } from "./Delete";
import { InputEventPair } from "./Input";

export type BaseEvent = {
event_type: string;
};

export type EventPair = CaretMoveEnterEventPair;
export type EventPair =
| CaretMoveEnterEventPair
| InputEventPair
| DeleteEventPair
| CombineEventPair;
38 changes: 34 additions & 4 deletions src/components/base/mix_editor/plugins/paragraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import {
import { EventPair } from "../event";
import { CaretMoveEnterEventResult } from "../event/CaretMoveEnter";
import { ToEnd } from "../selection";
import { MixEditorMouseEvent } from "../utils/types";
import { MixEditorMouseEvent } from "../utils/area";
import { find_ancestor_below, find_index_of_parent } from "@/common/dom";
import { DeleteEventResult } from "../event/Delete";

export type ParagraphBlockSavedData = {
inlines: InlineSavedData[];
Expand Down Expand Up @@ -71,6 +72,25 @@ export class ParagraphBlock<TInline extends Inline<any, any>>
if (!child) return CaretMoveEnterEventResult.skip;
return CaretMoveEnterEventResult.enter_child(actual_to);
}
} else if (event.event_type === "delete") {
const to = event.to;
if (event.type === "backward") {
if (to === 0) {
return DeleteEventResult.skip;
}
return DeleteEventResult.enter_child(to - 1);
} else if (event.type === "forward") {
if (to > this.children_count()) {
return DeleteEventResult.skip;
}
return DeleteEventResult.enter_child(to);
} else if (event.type === "specified") {
const from = event.from;
const curr_children = this.data.inlines.get();
const new_children = curr_children.splice(from, to - from);
this.data.inlines.set(new_children);
return DeleteEventResult.done(from + 1);
}
}
}
constructor(public data: { inlines: WrappedSignal<TInline[]> }) {}
Expand All @@ -82,7 +102,7 @@ export const Paragraph = (() => {
editor
) => {
const result = new ParagraphBlock({
inlines: createSignal<Inline[]>([]),
inlines: createSignal<Inline[]>([], { equals: false }),
});
const inlines = await editor.saver.load_areas(
"inline",
Expand Down Expand Up @@ -127,6 +147,17 @@ export const Paragraph = (() => {
const child_of_container = find_ancestor_below(e.target, container!);
if (!child_of_container) return;

const child_of_container_rect =
child_of_container.getBoundingClientRect();

let caret_before = true;
if (
e.x >
child_of_container_rect.left + child_of_container_rect.width / 2
) {
caret_before = false;
}

e.mix_selection_changed = true;

const index_in_container = find_index_of_parent(
Expand All @@ -136,10 +167,9 @@ export const Paragraph = (() => {

e.preventDefault();

console.log("collapsed_select", child_of_container, index_in_container);
props.editor.selection.collapsed_select({
area: block,
child_path: index_in_container,
child_path: index_in_container + (caret_before ? 0 : 1),
});

// TODO:需要考虑多行文本的情况,计算当前位于的行,然后取该行的高度
Expand Down
47 changes: 41 additions & 6 deletions src/components/base/mix_editor/plugins/text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import { MaybeArea, MixEditor } from "../MixEditor";
import { Plugin } from "../plugin";
import { create_InlineSaveData, InlineLoader } from "../save";
import { ToEnd } from "../selection";
import { MixEditorMouseEvent } from "../utils/types";
import { MixEditorMouseEvent } from "../utils/area";
import { InputEventResult } from "../event/Input";
import { DeleteEventResult } from "../event/Delete";

export type TextInlineSavedData = { value: string };

Expand Down Expand Up @@ -60,6 +62,44 @@ export class TextInline
} else {
return CaretMoveEnterEventResult.enter(to);
}
} else if (event.event_type === "input") {
const curr_value = this.data.value.get();
this.data.value.set(
curr_value.slice(0, event.to) + event.value + curr_value.slice(event.to)
);
return InputEventResult.done(event.to + event.value.length);
} else if (event.event_type === "delete") {
const curr_value = this.data.value.get();
let to = event.to;
if (to === ToEnd) {
to = curr_value.length;
}
if (event.type === "backward") {
const new_value = curr_value.slice(0, to - 1) + curr_value.slice(to);

if (new_value.length === 0) {
return DeleteEventResult.self_delete_required;
}
this.data.value.set(new_value);
return DeleteEventResult.done(to - 1);
} else if (event.type === "forward") {
const new_value = curr_value.slice(0, to) + curr_value.slice(to + 1);

if (new_value.length === 0) {
return DeleteEventResult.self_delete_required;
}
this.data.value.set(new_value);
return DeleteEventResult.done(to);
} else if (event.type === "specified") {
const new_value =
curr_value.slice(0, event.from) + curr_value.slice(to);

if (new_value.length === 0) {
return DeleteEventResult.self_delete_required;
}
this.data.value.set(new_value);
return DeleteEventResult.done(to);
}
}
}

Expand Down Expand Up @@ -124,16 +164,11 @@ export const Text = () => {
);
}

function handle_dblclick(e: MouseEvent) {
clear_dom_selection();
}

return (
<span
class="__inline __text"
ref={(it) => (container = it)}
onPointerDown={handle_pointer_down}
onDblClick={handle_dblclick}
>
{inline.data.value.get()}
</span>
Expand Down
4 changes: 2 additions & 2 deletions src/components/base/mix_editor/renderer/CaretRenderer.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@

animation: _m_mix_editor__caret_blink 1s step-start infinite;

& > input {
@apply w-0 p-0 m-0 border-none outline-none pointer-events-none;
& > .__inputer {
@apply max-w-0 p-0 m-0 border-none outline-none pointer-events-none overflow-hidden;
}
}
}
Expand Down
Loading

0 comments on commit eac61aa

Please sign in to comment.