feat(编辑能力): 完善继承样式编辑与受限提示,优化编辑入口和工具栏布局
build.yaml / build (push) Successful in 37s

This commit is contained in:
xiaoqidun committed 2026-09-16 13:27:05 +08:00
1 parent 237487b53c
commit 0034e0edbd
11 files changed
+234 -52

No files matched your search

+31 -31
View File
@@ -18,6 +18,7 @@
<div class="tool-group">
<button id="ofdButton" class="icon-button" type="button" title="打开" aria-label="打开" aria-keyshortcuts="Control+O Meta+O"><span class="open-icon" aria-hidden="true"></span></button>
<button id="newButton" class="icon-button" type="button" title="新建" aria-label="新建文档"><span class="new-icon" aria-hidden="true"></span></button>
<button id="editButton" class="icon-button" type="button" title="编辑文档" aria-label="编辑文档" aria-pressed="false" disabled><span class="edit-icon" aria-hidden="true"></span></button>
<button id="togglePagesButton" class="icon-button" type="button" title="页面栏" aria-label="页面栏" aria-pressed="true">&#9776;</button>
<button id="toggleMetaButton" class="icon-button" type="button" title="信息栏" aria-label="信息栏" aria-pressed="true">&#9432;</button>
</div>
@@ -43,7 +44,6 @@
<button id="annotationButton" class="button" type="button" data-short="注" title="关闭注解" aria-label="关闭注解" aria-pressed="true">注解</button>
</div>
<div class="tool-group">
<button id="editButton" class="button" type="button" data-short="编" title="编辑文档" aria-label="编辑文档" aria-pressed="false" disabled>编辑</button>
<select id="exportFormat" class="format-select" title="导出格式" aria-label="导出格式" disabled></select>
<select id="imageDPI" class="format-select dpi-select" title="图片 DPI" aria-label="图片 DPI" disabled>
<option value="96">96</option>
@@ -91,28 +91,13 @@
</select>
<span>对象</span>
</div>
<div class="editor-group editor-transform">
<select id="objectRotate" class="order-select" aria-label="对象旋转" disabled>
<option value="" hidden>旋转</option>
<option value="90">右转90°</option>
<option value="-90">左转90°</option>
</select>
<select id="objectFlip" class="order-select" aria-label="对象翻转" disabled>
<option value="" hidden>翻转</option>
<option value="horizontal">水平</option>
<option value="vertical">垂直</option>
</select>
<span>变换</span>
</div>
<div class="editor-group editor-image">
<select id="imageFit" aria-label="图片适应方式" disabled>
<option value="" hidden>缩放</option>
<option value="contain">适应</option>
<option value="cover">填充</option>
</select>
<button id="cropImageButton" class="button" type="button" aria-label="裁剪图片" aria-pressed="false" disabled>裁剪</button>
<button id="resetCropButton" class="button" type="button" aria-label="还原图片" disabled>还原</button>
<span>图片</span>
<div class="editor-group">
<button id="insertTextButton" class="button" type="button" aria-label="添加文字" aria-pressed="false">文字</button>
<button id="insertImageButton" class="button" type="button" aria-label="添加图片">图片</button>
<button id="drawLineButton" class="button" type="button" aria-label="绘制直线" aria-pressed="false">直线</button>
<button id="drawRectangleButton" class="button" type="button" aria-label="绘制矩形" aria-pressed="false">矩形</button>
<button id="drawEllipseButton" class="button" type="button" aria-label="绘制椭圆" aria-pressed="false">椭圆</button>
<span>插入</span>
</div>
<div class="editor-group">
<div class="text-font"><select id="textFont" aria-label="字体" title="字体" disabled></select><button id="textFontAdd" class="icon-button" type="button" title="添加字体" aria-label="添加字体">+</button></div>
@@ -133,20 +118,35 @@
<label class="editor-field"><span>字距</span><span class="editor-number"><input id="textSpacing" type="number" step="any" placeholder="0" aria-label="字距" title="字距" disabled><span>pt</span></span></label>
<span>段落</span>
</div>
<div class="editor-group">
<button id="insertTextButton" class="button" type="button" aria-label="添加文字" aria-pressed="false">文字</button>
<button id="insertImageButton" class="button" type="button" aria-label="添加图片">图片</button>
<button id="drawLineButton" class="button" type="button" aria-label="绘制直线" aria-pressed="false">直线</button>
<button id="drawRectangleButton" class="button" type="button" aria-label="绘制矩形" aria-pressed="false">矩形</button>
<button id="drawEllipseButton" class="button" type="button" aria-label="绘制椭圆" aria-pressed="false">椭圆</button>
<span>插入</span>
</div>
<div class="editor-group">
<div class="shape-paint"><label><input id="shapeFill" type="checkbox">填充</label><input id="shapeFillColor" class="color-input" type="color" value="#dcefeb" aria-label="填充颜色" title="填充颜色"></div>
<div class="shape-paint"><label><input id="shapeStroke" type="checkbox" checked>描边</label><input id="shapeStrokeColor" class="color-input" type="color" value="#000000" aria-label="描边颜色" title="描边颜色"></div>
<label class="editor-field"><span>线宽</span><span class="editor-number"><input id="shapeWidth" type="number" value="1" min="0" step="any" required aria-label="线宽" title="线宽"><span>pt</span></span></label>
<span>图形</span>
</div>
<div class="editor-group editor-image">
<select id="imageFit" aria-label="图片适应方式" disabled>
<option value="" hidden>缩放</option>
<option value="contain">适应</option>
<option value="cover">填充</option>
</select>
<button id="cropImageButton" class="button" type="button" aria-label="裁剪图片" aria-pressed="false" disabled>裁剪</button>
<button id="resetCropButton" class="button" type="button" aria-label="还原图片" disabled>还原</button>
<span>图片</span>
</div>
<div class="editor-group editor-transform">
<select id="objectRotate" class="order-select" aria-label="对象旋转" disabled>
<option value="" hidden>旋转</option>
<option value="90">右转90°</option>
<option value="-90">左转90°</option>
</select>
<select id="objectFlip" class="order-select" aria-label="对象翻转" disabled>
<option value="" hidden>翻转</option>
<option value="horizontal">水平</option>
<option value="vertical">垂直</option>
</select>
<span>变换</span>
</div>
<div class="editor-group">
<button id="addPageButton" class="button" type="button" aria-label="新增页面">新增</button>
<button id="copyPageButton" class="button" type="button" aria-label="复制页面">复制</button>
+32
View File
@@ -374,6 +374,34 @@ button:not(:disabled):hover {
transform: translate(-50%, -50%) rotate(90deg);
}
.edit-icon {
position: relative;
width: 15px;
height: 15px;
transform: rotate(-45deg);
}
.edit-icon::before {
position: absolute;
top: 5px;
left: 4px;
width: 9px;
height: 3px;
border: 1px solid currentColor;
content: "";
}
.edit-icon::after {
position: absolute;
top: 5px;
left: 0;
width: 4px;
height: 5px;
background: currentColor;
clip-path: polygon(0 50%, 100% 0, 100% 100%);
content: "";
}
.toolbar-space {
flex: 1 1 auto;
}
@@ -1305,6 +1333,10 @@ button.outline-link:hover {
outline: calc(1px / var(--surface-scale)) solid var(--accent);
}
.edit-object.read-only {
cursor: default;
}
.edit-line::before {
position: absolute;
inset: calc(-4px / var(--surface-scale));
+9 -2
View File
@@ -1,4 +1,4 @@
import { CanvasEditor, canEditObject } from "./ofdgo_edit.js";
import { CanvasEditor, canEditObject, objectEditReason } from "./ofdgo_edit.js";
import { FontManager } from "./ofdgo_font.js";
const MM_TO_PX = 96 / 25.4;
@@ -250,7 +250,14 @@ const canvasEditor = new CanvasEditor(el.viewerPanel, {
busy: () => document.body.hasAttribute("aria-busy"),
rotation: () => state.rotation,
canInsert: (index) => pageCan("insert", index),
onSelect: updateObjectControls,
onSelect: (item, previous) => {
updateObjectControls(item);
const reason = objectEditReason(item), previousReason = objectEditReason(previous);
if (state.editing && state.doc && !document.body.hasAttribute("aria-busy")
&& (reason || previousReason && el.statusText.textContent === previousReason)) {
setStatus(reason || pageStatus(state.pageIndex, state.doc.pageCount));
}
},
onTransform: (item, change) => changeDocument(item.items ? "ofdgoTransformObjects" : "ofdgoTransformObject", item,
change.x + item.x * (1 - change.scale), change.y + item.y * (1 - change.scale), change.scale),
onReshape: (item, box) => changeDocument("ofdgoReshapeObject", item, box.x, box.y, box.width, box.height),
+20 -1
View File
@@ -122,6 +122,22 @@ export function canEditObject(item, capability) {
return Boolean(item) && (item.items || [item]).every(object => Boolean(object.capabilities?.[capability]));
}
export function objectEditReason(item) {
const reasons = (item?.items || (item ? [item] : [])).map(object => {
const reason = object.capabilities?.reason || "";
if (!reason) return "";
let message = "对象特性暂不支持";
if (reason.includes("embedded font")) message = "缺少内嵌字体";
else if (reason.includes("does not contain")) message = "字体缺少所需文字";
else if (reason.includes("color") || reason.includes("RGB")) message = "颜色样式暂不支持";
else if (reason.includes("draw parameter")) message = "绘制参数暂不支持";
else if (reason.includes("layer")) message = "图层特性暂不支持";
else if (reason.includes("invalid")) message = "对象数据异常";
return `${object.capabilities.transform ? "部分操作受限" : "暂不可编辑"}:${message}`;
});
return [...new Set(reasons.filter(Boolean))].join(";");
}
export class CanvasEditor {
constructor(viewer, options) {
this.viewer = viewer;
@@ -182,6 +198,8 @@ export class CanvasEditor {
for (const object of page.objects) {
const node = document.createElement("div");
node.className = "edit-object";
node.classList.toggle("read-only", !canEditObject(object, "transform"));
node.title = objectEditReason(object);
if (object.shape === "line") {
node.classList.add("edit-line");
}
@@ -269,6 +287,7 @@ export class CanvasEditor {
}
setSelection(items) {
const previous = this.selected;
if (this.crop && (items.length !== 1 || items[0] !== this.crop.item)) this.closeCrop();
if (this.selected?.items) this.selected.node.remove();
for (const item of this.items()) {
@@ -296,7 +315,7 @@ export class CanvasEditor {
item.node.classList.toggle("multi-selected", items.length > 1);
item.node.setAttribute("aria-pressed", "true");
}
this.options.onSelect(this.selected);
this.options.onSelect(this.selected, previous);
}
select(item, add = false) {