Skip to content

feat(svg-editor): 多元素选择、框选、元素属性展示#85

Merged
hugohe3 merged 10 commits into
hugohe3:mainfrom
WodenJay:feat/visual-edit-multi-select
May 2, 2026
Merged

feat(svg-editor): 多元素选择、框选、元素属性展示#85
hugohe3 merged 10 commits into
hugohe3:mainfrom
WodenJay:feat/visual-edit-multi-select

Conversation

@WodenJay

@WodenJay WodenJay commented May 2, 2026

Copy link
Copy Markdown
Contributor

概要

#84 的基础上,为 SVG 视觉编辑器添加多元素批量操作能力。用户现在可以通过 Ctrl+click、框选或 Ctrl+A 一次性选择多个元素,并对选中元素统一添加标注。同时,右侧面板新增元素属性展示(字体、字号、颜色、位置等),帮助用户更精准地描述修改意图。

Closes #84

新增功能

1. 多元素选择

  • Ctrl+click:按住 Ctrl 点击元素进行加选/取消(toggle)
  • 框选(Rubber Band):在空白区域按住鼠标左键拖拽,框选范围内所有元素
  • Ctrl+A:全选当前幻灯片所有元素
  • Escape:清除当前选择(在标注输入框内按 Escape 不会误清除)

2. 统一批量标注

  • 选中多个元素后,一条标注自动应用到所有选中元素
  • 通过 Promise.all 并行调用现有 API,无新增后端接口

3. 元素属性展示

  • 单选:显示元素的 position、size、font、font-size、fill、stroke 等属性,颜色值带色块预览
  • 多选:显示类型统计(如 "3 text, 2 rect")、共享 font-size、元素列表

修改文件

文件 改动 说明
scripts/svg_editor/static/app.js +405/-44 核心逻辑:多选状态模型、框选碰撞检测、快捷键、属性提取、批量标注
scripts/svg_editor/static/index.html +2 新增 rubber-band-overlay 和 element-props 容器
scripts/svg_editor/static/style.css +103 框选样式、属性表格、多选摘要面板

无后端改动,无新增依赖。

测试

自动化测试(20 项,全部通过)

编号 测试内容 覆盖点
T1 Ctrl+A 全选(元素点击后) textarea 不应自动聚焦拦截快捷键
T2 textarea 不自动聚焦 updateSelectionPanel 回归防护
T3 框选创建 + overlay 激活 mousedown 启动框选流程
T4 框选选中范围内元素 getBBox + getScreenCTM 碰撞检测
T5 单击元素正常选中 框选不干扰普通点击
T6 Escape 在 textarea 聚焦时保留选择 Escape handler guard
T7 Escape 在非聚焦时清除选择 正常清除行为
T8 点击-清除-点击无重复选中 事件监听器无累积
T9 Ctrl+click 多选 Set 状态管理
T10 框选替换已有选择 无 Ctrl 时清除旧选择
T11 Ctrl+click 取消选中 toggle 逻辑
T12 单选显示属性表格 getElementProperties + renderPropertyTable
T13 多选显示摘要 renderMultiSelectSummary
T14 点击空白清除选择 SVG 背景 click handler
T15 阈值内拖拽不触发框选 5px 对角线阈值
T16 Ctrl+框选追加选择 框选 + modifier key
T17 操作后 Set 与 DOM 一致性 状态同步
T18 metaKey (Mac Cmd) 等效 跨平台兼容
T19 清除后面板/属性隐藏 UI 清理

人工测试

  • 使用示例项目启动编辑器,在浏览器中实际操作:单选、多选、框选、添加标注、保存
  • 从头创建 PPT 项目并测试编辑器 API 端点(slides、annotate、save-all)

安全改进

  • sanitizeSvg 移除 javascript: 和 data: 协议的 href
  • 颜色属性渲染使用 isSafeColor() 验证,防止 CSS 注入
  • 框选阈值使用对角线距离,mousemove/mouseup 逻辑一致

WodenJay added 10 commits May 2, 2026 19:29
Replace single-element selection (selectedElementId) with Set-based
multi-selection (selectedElementIds). Add updateSelectionPanel for
single/multi/empty states, batch annotation via Promise.all.
- Only pre-fill annotation textarea for single-select, clear for multi
- Add .multi-count CSS class for multi-select count display
Add click-drag rubber band selection on empty space. Uses HTML overlay
for drawing the selection rectangle and SVG getBBox + getScreenCTM for
screen-space collision detection. Supports Ctrl+drag for additive mode.
5px threshold prevents accidental activation on clicks.
- Add cancelRubberBand() helper, call from selectSlide to reset state
- Remove unused addToSelection parameter from selectByRubberBand
- Fix overlay variable shadowing in mouseup handler
- Clear selection on below-threshold click (preserves original behavior)
Ctrl+A selects all SVG elements (skips when textarea focused).
Escape clears current selection.
Show element properties in right panel on selection:
- Text: font, font-size, font-weight, fill, anchor, content preview
- Shapes: fill, stroke, dimensions
- Images: filename, dimensions
- Multi-select: type count summary, shared font-size, element list
- Remove annotationText.focus() from updateSelectionPanel — it was
  causing Ctrl+A to be intercepted by the textarea guard, and
  interfering with rubber band drag operations
- Fix rubber band mousedown check: use closest('.svg-selectable')
  instead of direct classList check, so clicking on SVG background
  or container elements correctly starts rubber band
- Add rubberBandUsed flag to prevent SVG click handler from clearing
  selection after a rubber band operation completes
Rubber band:
- Remove closest('.svg-selectable') guard from mousedown — it failed
  when SVG had a background rect covering the canvas (all clicks
  matched the guard, preventing rubber band from starting)
- Start tracking on every mousedown, only activate overlay and set
  rubberBandUsed flag when mousemove exceeds threshold
- This allows element clicks to still work (below threshold) while
  rubber band works for any drag (regardless of target element)

Other fixes:
- Add textarea focus guard to Escape handler — prevents clearing
  selection when user is typing in annotation textarea
- Make rubber band more visible: 2px dashed border, higher opacity
Add XSS protection to sanitizeSvg — strip href/xlink:href attributes
that contain javascript: protocol from <a> elements. Prevents potential
XSS via crafted SVG content.
Security:
- Block data: URIs in sanitizeSvg (defense-in-depth against XSS)
- Validate CSS color values before inserting into style attributes
  to prevent CSS injection via crafted SVG fill/stroke values

Bug fixes:
- Ctrl+click on empty space now respects modifier key (was clearing
  selection unconditionally)

Improvements:
- Use diagonal distance for rubber band threshold (consistent between
  mousemove and mouseup handlers)
- Cache #element-props in DOM refs block (consistent with pattern)
- Add max-height/overflow-y to property panel (prevents layout push)
Copilot AI review requested due to automatic review settings May 2, 2026 11:59
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@hugohe3 hugohe3 merged commit 01b1564 into hugohe3:main May 2, 2026
3 of 4 checks passed
hugohe3 added a commit that referenced this pull request May 13, 2026
Add a feature highlight ahead of "Template Replication" in both READMEs
describing the in-browser live preview + annotation flow, and crediting
@WodenJay's PR #85 as the foundation this work builds on.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GUI局部编辑的体验优化

3 participants