mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-16 20:02:20 +02:00
feat(photo-editor): Ajax Image Editor client (Photoshop-parity tools)
Photoshop-parity editor client: layers, selection/masking, brushes,
smart filters, LUT import, Quick Edit, mobile gestures, workspaces,
batch operations, and the service-worker versioning (v978->v1006).
Source: vendor/photocraft (ad1cff44) photo-editor client files, merged
onto upstream/dev (a8c147b2). Conflict resolution documented in
pr_work/A_conflicts.md; editor files taken from vendor/photocraft
(upstream's editor is an earlier snapshot of the same feature).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c74f475679
commit
5fac693c1c
@@ -0,0 +1,53 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SRC = ROOT / "static/js/editor/clipboard-and-drop.js"
|
||||
|
||||
|
||||
def test_editor_paste_owns_clipboard_images_while_editor_open():
|
||||
"""Pasting an image with the editor open must not fall through to chat.
|
||||
|
||||
The editor wires a window-capture ``paste`` listener. The global chat
|
||||
attach listener in ``static/app.js`` also listens on ``window`` (bubble)
|
||||
and stages any ``item.kind === 'file'`` clipboard entry into the chatbox.
|
||||
If the editor's listener bailed whenever the event target was *any*
|
||||
input/textarea/``[role=dialog]``, then a paste whose focus sat on the chat
|
||||
composer behind the editor modal went to the chatbox and never reached the
|
||||
canvas. The guard must only defer to editable fields that live INSIDE the
|
||||
editor container — and it must NOT include ``[role=dialog]``, because the
|
||||
editor itself sits inside the gallery modal's ``role=dialog`` content.
|
||||
"""
|
||||
src = SRC.read_text(encoding="utf-8")
|
||||
assert "wireClipboardAndDrop" in src
|
||||
# The bail-out must be scoped to targets inside the editor.
|
||||
assert "const insideEditor = target && container && container.contains(target);" in src
|
||||
assert (
|
||||
"if (insideEditor && (target.isContentEditable || "
|
||||
"target.closest?.('input, textarea, select'))) return;"
|
||||
) in src
|
||||
# ``[role="dialog"]`` must NOT appear in the paste guard (the editor lives
|
||||
# inside the gallery modal's role=dialog modal-content).
|
||||
assert "target.closest?.('input, textarea, select, [role=\"dialog\"]')" not in src
|
||||
# Capture-phase interception must preventDefault + stop the chat listener.
|
||||
assert "e.stopImmediatePropagation();" in src
|
||||
assert "e.preventDefault();" in src
|
||||
|
||||
|
||||
def test_editor_ctrl_v_pastes_the_internal_selection_clipboard():
|
||||
"""Ctrl/Cmd+V must paste the internal selection clipboard directly.
|
||||
|
||||
On an insecure origin (the editor is served over http on a LAN IP)
|
||||
``navigator.clipboard`` is unavailable, so copying a selection never reaches
|
||||
the OS clipboard and the native ``paste`` event carries stale ``text/plain``
|
||||
-> the internal-paste branch is skipped and Ctrl+V silently does nothing
|
||||
(the toolbar buttons worked because they bypass the event entirely).
|
||||
"""
|
||||
src = SRC.read_text(encoding="utf-8")
|
||||
# Shared, hoisted paste helper (also used by the native paste listener).
|
||||
assert "const pasteAsLayer = (imgSource, label, offset = { x: 0, y: 0 }) => {" in src
|
||||
# Explicit keyboard handler for the internal clipboard.
|
||||
assert "window.addEventListener('keydown', (e) => {" in src
|
||||
assert "if (String(e.key || '').toLowerCase() !== 'v') return;" in src
|
||||
assert "if (!state.internalClipboard) return;" in src
|
||||
assert "pasteAsLayer(state.internalClipboard, 'Pasted Selection'" in src
|
||||
Reference in New Issue
Block a user