Show saved attachments in editor and after stale edits
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a192e9-c28b-7533-bf25-e2341364c22a
This commit is contained in:
parent
1139a6d30e
commit
31eaa581a6
4 changed files with 84 additions and 4 deletions
|
|
@ -377,6 +377,14 @@ def page_edit(request: HttpRequest, slug: str, page_slug: str = "index") -> Http
|
|||
return redirect("spaces:detail", slug=slug)
|
||||
|
||||
doc = pageops.read_page(space.slug, page_slug)
|
||||
# Show files from the saved page even when a rejected edit redisplays the
|
||||
# submitted fields. Attachment mutations keep their own revision checks.
|
||||
try:
|
||||
saved_files = attachments.page_files(space.slug, doc) if doc.exists else []
|
||||
attachment_error = ""
|
||||
except (pageops.PageOpsError, OSError) as exc:
|
||||
saved_files = []
|
||||
attachment_error = _mutation_error(exc)
|
||||
if request.method == "POST":
|
||||
title = (request.POST.get("title") or "").strip()
|
||||
abstractor = (request.POST.get("abstractor") or "").strip()
|
||||
|
|
@ -421,6 +429,8 @@ def page_edit(request: HttpRequest, slug: str, page_slug: str = "index") -> Http
|
|||
"submitted_slug": request.POST.get("slug", page_slug),
|
||||
"is_new": not doc.exists,
|
||||
"form_title": "Edit or rename page" if doc.exists else "New page",
|
||||
"saved_files": saved_files,
|
||||
"attachment_error": attachment_error,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,30 @@
|
|||
<h1>{{ form_title }}</h1>
|
||||
<p class="muted">Change the title to rename the displayed protocol. Change the file name below to rename its Markdown file and page address.</p>
|
||||
|
||||
{% if not is_new %}
|
||||
<section class="card" aria-labelledby="editor-attachments-title">
|
||||
<h2 id="editor-attachments-title">Attachments</h2>
|
||||
<p class="muted">Files on the saved page. Editing or renaming the page keeps its attachments.</p>
|
||||
{% if attachment_error %}
|
||||
<p>{{ attachment_error }}</p>
|
||||
{% else %}
|
||||
<ul>
|
||||
{% for file in saved_files %}
|
||||
<li>
|
||||
<strong>{{ file.name }}</strong>
|
||||
{% if file.open_url %}<a href="{{ file.open_url }}" target="_blank" rel="noopener">Open image</a>{% endif %}
|
||||
<a href="{{ file.download_url }}">Download</a>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li>No attachments on this page.</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<a href="{% url 'spaces:page_attachments' space.slug doc.slug %}" target="_blank" rel="noopener">Manage attachments (new tab)</a>
|
||||
<p class="muted">Save your edits before adding or removing files, then reopen the editor.</p>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" class="card" style="margin-top:1rem;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="revision" value="{{ doc.revision }}">
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ try {
|
|||
? 'Open, download or manage attachments' : 'Open or download attachments', exact: true}).click();
|
||||
await page.waitForURL('**/attachments/');
|
||||
}
|
||||
async function downloadedBytes() {
|
||||
const pending = page.waitForEvent('download');
|
||||
await page.getByRole('link', {name: 'Download', exact: true}).click();
|
||||
async function downloadedBytes(tab = page) {
|
||||
const pending = tab.waitForEvent('download');
|
||||
await tab.getByRole('link', {name: 'Download', exact: true}).click();
|
||||
const stream = await (await pending).createReadStream();
|
||||
const chunks = [];
|
||||
for await (const chunk of stream) chunks.push(chunk);
|
||||
|
|
@ -50,6 +50,35 @@ try {
|
|||
assert.match(await page.locator('main').innerText(), /Attachment added/);
|
||||
const originalDownload = await page.getByRole('link', {name: 'Download', exact: true}).getAttribute('href');
|
||||
assert.equal(await downloadedBytes(), 'Synthetic attachment bytes');
|
||||
const editUrl = `${shared}pages/notes/edit/`;
|
||||
await page.goto(editUrl);
|
||||
const files = page.getByRole('region', {name: 'Attachments', exact: true});
|
||||
assert.match(await files.innerText(), /meeting-notes.txt/);
|
||||
assert.equal(await downloadedBytes(), 'Synthetic attachment bytes');
|
||||
await page.getByLabel('Body (markdown)').fill('Unsaved author notes');
|
||||
const opened = context.waitForEvent('page');
|
||||
await page.getByRole('link', {name: 'Manage attachments (new tab)', exact: true}).click();
|
||||
const management = await opened;
|
||||
await management.waitForURL('**/attachments/');
|
||||
assert.equal(await page.getByLabel('Body (markdown)').inputValue(), 'Unsaved author notes');
|
||||
assert.match(await management.locator('main').innerText(), /meeting-notes.txt/);
|
||||
await management.close();
|
||||
const stale = await context.newPage();
|
||||
await stale.goto(editUrl);
|
||||
assert.match(await stale.getByRole('region', {name: 'Attachments', exact: true}).innerText(), /meeting-notes.txt/);
|
||||
await stale.getByLabel('Body (markdown)').fill('Keep my stale edit');
|
||||
await page.getByRole('button', {name: 'Save page', exact: true}).click();
|
||||
await page.waitForURL('**?page=notes');
|
||||
await stale.getByRole('button', {name: 'Save page', exact: true}).click();
|
||||
await stale.waitForURL('**/edit/');
|
||||
assert.match(await stale.locator('main').innerText(), /This page changed/);
|
||||
assert.equal(await stale.getByLabel('Body (markdown)').inputValue(), 'Keep my stale edit');
|
||||
assert.match(await stale.getByRole('region', {name: 'Attachments', exact: true}).innerText(), /meeting-notes.txt/);
|
||||
assert.equal(await downloadedBytes(stale), 'Synthetic attachment bytes');
|
||||
await stale.close();
|
||||
await page.goto(editUrl);
|
||||
assert.equal(await page.getByLabel('Body (markdown)').inputValue(), 'Unsaved author notes\n');
|
||||
assert.match(await files.innerText(), /meeting-notes.txt/);
|
||||
await login('file-reader');
|
||||
await menu(source, false);
|
||||
assert.equal(await page.getByRole('button', {name: 'Add attachment', exact: true}).count(), 0);
|
||||
|
|
|
|||
|
|
@ -151,7 +151,24 @@ def test_inline_body_files_have_authenticated_download_links(people, client):
|
|||
def test_edit_and_rename_keep_attachment_associations(people, client):
|
||||
space, doc, _, _, _ = people
|
||||
added = upload(space, doc)
|
||||
response = client.post(reverse("spaces:page_edit", args=[space.slug, doc.slug]), {
|
||||
edit_url = reverse("spaces:page_edit", args=[space.slug, doc.slug])
|
||||
response = client.get(edit_url)
|
||||
assert response.status_code == 200
|
||||
assert len(response.context["saved_files"]) == 1
|
||||
download = response.context["saved_files"][0]["download_url"]
|
||||
assert download.encode() in response.content
|
||||
# Another tab changes the page after this editor opened. Redisplaying the
|
||||
# rejected text must still expose the saved attachments without losing them.
|
||||
stale = client.post(edit_url, {
|
||||
"title": "Older edit", "slug": doc.slug, "body": "Unsaved text",
|
||||
"revision": doc.revision,
|
||||
})
|
||||
assert stale.status_code == 200
|
||||
assert b"This page changed" in stale.content
|
||||
assert stale.context["doc"].body == "Unsaved text"
|
||||
assert download.encode() in stale.content
|
||||
assert pageops.read_page(space.slug, doc.slug).revision == added.revision
|
||||
response = client.post(edit_url, {
|
||||
"title": "Renamed notes", "slug": "renamed", "body": "Updated notes",
|
||||
"revision": added.revision,
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue