Attach images pasted into the composer, comments and chat

Pressing Ctrl+V with a screenshot on the clipboard now attaches it
immediately instead of requiring a trip through the file picker.

The clipboard reader lives in dp-upload behind a new opt-in `paste`
boolean attribute: with it set, the component binds one paste listener
on its closest form and routes the clipboard image files through the
same handleFiles path as the picker and the drop target, so validation,
limits, the terms gate and the hidden attachment_uids field are shared.
A paste carrying plain text is never swallowed.

It is opt-in rather than a form-wide default because a form may hold
several upload buttons - projects.html has cover and logo beside the
attachment one - and a default would attach one pasted screenshot to
all of them.

Set on _attachment_form.html, so every form including it inherits the
behaviour (post composer, post edit, gists, projects, issues,
screenshots), plus _comment_form.html, messages.html and the embed-mode
skeleton AppChat builds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-17 22:23:25 +02:00
co-authored by Claude Opus 5
parent e0672f896d
commit 076f55f380
12 changed files with 103 additions and 6 deletions
+19 -1
View File
@@ -1,6 +1,6 @@
# retoor <retoor@molodetz.nl>
from tests.conftest import BASE_URL
from tests.conftest import BASE_URL, paste_image
import time
import requests
from playwright.sync_api import expect
@@ -702,6 +702,24 @@ def test_feed_politics_topic(alice):
assert politics_link.is_visible()
def test_paste_image_attaches_in_post_composer(alice):
page, _ = alice
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
page.locator(".feed-fab").first.wait_for(state="visible", timeout=10000)
page.locator(".feed-fab").first.click()
page.locator("#post-content").wait_for(state="visible", timeout=10000)
page.locator("#create-post-modal dp-upload .dp-upload-btn").first.wait_for(
state="visible", timeout=10000
)
paste_image(page, "#post-content")
page.locator("#create-post-modal dp-upload .dp-upload-count").first.wait_for(
state="visible", timeout=15000
)
expect(
page.locator("#create-post-modal dp-upload input[name='attachment_uids']")
).to_have_value(re.compile(r".+"))
def test_create_post_cancel_modal(alice):
page, _ = alice
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
+19 -1
View File
@@ -3,7 +3,7 @@
import re
import time
from playwright.sync_api import expect
from tests.conftest import BASE_URL
from tests.conftest import BASE_URL, paste_image
from devplacepy.database import get_table
import requests
def _seed_news_seo():
@@ -183,6 +183,24 @@ def test_send_message_appears_in_thread(alice):
page.locator(f".message-bubble:has-text('{msg}')").first.wait_for(state="visible")
def test_paste_image_attaches_in_chat(alice):
page, _ = alice
bob = get_table("users").find_one(username="bob_test")
page.goto(
f"{BASE_URL}/messages?with_uid={bob['uid']}", wait_until="domcontentloaded"
)
page.locator(".messages-input-area dp-upload .dp-upload-btn").first.wait_for(
state="visible", timeout=10000
)
paste_image(page, ".messages-input-area textarea[name='content']")
page.locator(".messages-input-area dp-upload .dp-upload-count").first.wait_for(
state="visible", timeout=15000
)
expect(
page.locator(".messages-input-area dp-upload input[name='attachment_uids']")
).to_have_value(re.compile(r".+"))
def test_messages_page_loads(alice):
page, _ = alice
page.goto(f"{BASE_URL}/messages", wait_until="domcontentloaded")
+16 -1
View File
@@ -2,7 +2,7 @@
import re
from playwright.sync_api import expect
from tests.conftest import BASE_URL, assert_share_copies
from tests.conftest import BASE_URL, assert_share_copies, paste_image
def create_post(page, topic="random", content="Test post content", title=None):
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
page.locator(".feed-fab").first.wait_for(state="visible", timeout=10000)
@@ -375,6 +375,21 @@ def test_emoji_picker_opens(alice):
assert page.locator("emoji-picker").first.is_visible()
def test_paste_image_attaches_in_comment_form(alice):
page, _ = alice
create_post(page, "random", "Post for pasted comment attachment")
page.locator(".comment-form dp-upload .dp-upload-btn").first.wait_for(
state="visible", timeout=10000
)
paste_image(page, ".comment-form textarea[name='content']")
page.locator(".comment-form dp-upload .dp-upload-count").first.wait_for(
state="visible", timeout=15000
)
expect(
page.locator(".comment-form dp-upload input[name='attachment_uids']")
).to_have_value(re.compile(r".+"))
def test_attachment_upload_ui(alice):
import io
from PIL import Image