feat(编辑能力): 优化富文本选择与编辑
build.yaml / build (push) Successful in 50s

This commit is contained in:
xiaoqidun committed 2026-10-14 16:11:42 +08:00
1 parent de229c163b
commit a182f025e1
3 files changed
+50 -11

No files matched your search

+5 -2
View File
@@ -2708,7 +2708,7 @@ button.outline-link:hover {
z-index: 1; z-index: 1;
margin: -2px; margin: -2px;
padding: 1px; padding: 1px;
border: 1px solid var(--accent); border: 1px dashed var(--accent);
border-radius: 0; border-radius: 0;
background: #fff; background: #fff;
outline: none; outline: none;
@@ -2776,7 +2776,10 @@ button.outline-link:hover {
cursor: move; cursor: move;
} }
.edit-object:hover, .edit-object:hover {
outline: calc(1px / var(--surface-scale)) dashed var(--accent);
}
.edit-object.selected, .edit-object.selected,
.edit-object.erasing, .edit-object.erasing,
.edit-selection, .edit-selection,
+41 -9
View File
@@ -551,7 +551,10 @@ export class CanvasEditor {
viewer.addEventListener("blur", () => this.commitNudge()); viewer.addEventListener("blur", () => this.commitNudge());
viewer.addEventListener("dblclick", (event) => { viewer.addEventListener("dblclick", (event) => {
const item = this.selected; const item = this.selected;
if (!event.altKey && this.enabled && item && !item.items && !this.input && !this.options.busy()) { const target = event.target === viewer ? document.elementFromPoint?.(event.clientX, event.clientY) : event.target;
if (!event.altKey && this.enabled && item && !item.items && !this.input && !this.options.busy()
&& target && (!target.dataset.corner || item.type === "TextObject")
&& this.targetAt({ target, clientX: event.clientX, clientY: event.clientY }) === item) {
this.options.onEdit(item); this.options.onEdit(item);
} }
}); });
@@ -640,7 +643,10 @@ export class CanvasEditor {
item.contour = { svg, path: contour }; item.contour = { svg, path: contour };
} }
this.nodes.set(node, item); this.nodes.set(node, item);
node.addEventListener("focus", () => this.select(item)); node.addEventListener("focus", () => {
if (this.input) this.selectAfterText(item);
else this.select(item);
});
this.place(item); this.place(item);
layer.append(node); layer.append(node);
if (selected.has(object.id)) { if (selected.has(object.id)) {
@@ -830,7 +836,13 @@ export class CanvasEditor {
return; return;
} }
if (this.input) { if (this.input) {
if (!this.input.input.contains(event.target)) this.commitText(); if (event.button === 0 && !this.input.input.contains(event.target)) {
if (this.input.composing) this.commitText();
else {
event.preventDefault();
this.selectAfterText(this.targetAt(event));
}
}
return; return;
} }
if (!this.enabled || this.options.busy() || this.nudgeCommit || event.button !== 0 || this.drag) { if (!this.enabled || this.options.busy() || this.nudgeCommit || event.button !== 0 || this.drag) {
@@ -859,11 +871,8 @@ export class CanvasEditor {
this.cycleSelection(event); this.cycleSelection(event);
return; return;
} }
let node = event.target.closest(".edit-object, .edit-selection"); const target = this.targetAt(event), node = target?.node;
const direct = this.nodes.get(node); if (event.target.closest(".edit-object, .edit-selection") || target) event.preventDefault();
const target = event.target.dataset.corner || direct && !direct.contours ? direct : this.itemsAt(event)[0];
if (node || target) event.preventDefault();
node = target?.node;
this.focus(); this.focus();
const additive = event.shiftKey || event.ctrlKey || event.metaKey || this.multiple; const additive = event.shiftKey || event.ctrlKey || event.metaKey || this.multiple;
if (!target) { if (!target) {
@@ -909,6 +918,16 @@ export class CanvasEditor {
|| tolerance > 0 && this.hitContext.isPointInStroke(path, point.x, point.y)); || tolerance > 0 && this.hitContext.isPointInStroke(path, point.x, point.y));
} }
targetAt(event) {
if (event.target === this.viewer) {
const target = document.elementFromPoint?.(event.clientX, event.clientY);
if (!target || target === this.viewer) return;
event = { target, clientX: event.clientX, clientY: event.clientY };
}
const direct = this.nodes.get(event.target.closest(".edit-object, .edit-selection"));
return event.target.dataset.corner || direct && !direct.contours ? direct : this.itemsAt(event)[0];
}
itemsAt(event) { itemsAt(event) {
const surface = event.target.closest(".page-surface"); const surface = event.target.closest(".page-surface");
const page = this.pages.get(surface); const page = this.pages.get(surface);
@@ -916,7 +935,19 @@ export class CanvasEditor {
const rect = surface.getBoundingClientRect(), rotation = this.options.rotation(); const rect = surface.getBoundingClientRect(), rotation = this.options.rotation();
const point = pagePoint(event.clientX, event.clientY, rect, page, rotation); const point = pagePoint(event.clientX, event.clientY, rect, page, rotation);
const tolerance = 3 * page.width / (rotation % 180 ? rect.height : rect.width); const tolerance = 3 * page.width / (rotation % 180 ? rect.height : rect.width);
return [...surface.querySelectorAll(".edit-object")].map(node => this.nodes.get(node)).reverse().filter(item => this.contains(item, point, tolerance)); return [...surface.querySelectorAll(".edit-object")].map(node => this.nodes.get(node)).reverse().filter(item => item.type === "TextObject"
? point.x >= item.x - tolerance && point.x <= item.x + item.width + tolerance
&& point.y >= item.y - tolerance && point.y <= item.y + item.height + tolerance
: this.contains(item, point, tolerance));
}
async selectAfterText(target) {
if (!await this.commitText() || this.input || !this.enabled || this.options.busy() || this.tool) return;
if (target) {
const item = this.pageItems(target.index).find(item => item.id === target.id);
if (item) this.select(item);
}
this.focus();
} }
pageItems(index) { pageItems(index) {
@@ -1561,6 +1592,7 @@ export class CanvasEditor {
editing.rich?.remember(); editing.rich?.remember();
const selection = document.getSelection(); const selection = document.getSelection();
if (selection?.rangeCount && input.contains(selection.anchorNode)) editing.range = selection.getRangeAt(0).cloneRange(); if (selection?.rangeCount && input.contains(selection.anchorNode)) editing.range = selection.getRangeAt(0).cloneRange();
if (event.relatedTarget?.closest(".edit-object") && this.viewer.contains(event.relatedTarget)) return;
if (!this.options.textControls?.some(control => control === event.relatedTarget || control.contains(event.relatedTarget)) if (!this.options.textControls?.some(control => control === event.relatedTarget || control.contains(event.relatedTarget))
&& !this.options.keepTextFocus?.() && !this.options.busy()) this.commitText(); && !this.options.keepTextFocus?.() && !this.options.busy()) this.commitText();
}); });
+4
View File
@@ -2803,6 +2803,10 @@ func editorRichText(page int, item map[string]any, object ofdgo.GraphicObject, v
} }
capabilities["enter"], capabilities["ungroup"] = false, false capabilities["enter"], capabilities["ungroup"] = false, false
capabilities["stretch"] = false capabilities["stretch"] = false
if capabilities["reasonCode"] == string(ofdgo.EditUnsupportedObject) && capabilities["transform"] == true && capabilities["copy"] == true && object.CompositeGraphicUnit.Clips == nil {
delete(capabilities, "reason")
delete(capabilities, "reasonCode")
}
} }
// getRichText 读取文字分段与段落选项,颜色转换为浏览器RGB格式 // getRichText 读取文字分段与段落选项,颜色转换为浏览器RGB格式