From f6c5a3753afc8fa10827761e2c78376d2c28a299 Mon Sep 17 00:00:00 2001 From: Max Berger Date: Sun, 15 Mar 2026 23:21:17 +0100 Subject: [PATCH] Display incoming share owner and hide some action buttons --- integ_tests/test_sharing.py | 30 ++++++++++++++++--- radicale/web/internal_data/index.html | 1 + .../js/scenes/CollectionsScene.js | 20 ++++++++++++- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/integ_tests/test_sharing.py b/integ_tests/test_sharing.py index ecc667c9..6138514f 100644 --- a/integ_tests/test_sharing.py +++ b/integ_tests/test_sharing.py @@ -297,7 +297,8 @@ def test_share_by_map_validation(page: Page, radicale_server: str) -> None: ).to_have_count(1) -def test_incoming_shares(page: Page, radicale_server: str) -> None: +@pytest.mark.parametrize("permissions", ["ro", "rw"]) +def test_incoming_shares(page: Page, radicale_server: str, permissions: str) -> None: # 1. Admin logs in and creates a map share for 'max' login(page, radicale_server) create_collection(page, radicale_server) @@ -307,6 +308,8 @@ def test_incoming_shares(page: Page, radicale_server: str) -> None: page.click('button[data-name="sharebymap"]') page.locator('input[data-name="shareuser"]').fill("max") page.locator('input[data-name="sharehref"]').fill("mapped") + if permissions == "rw": + page.check("#newshare_attr_permissions_rw") page.click('#newshare button[data-name="submit"]') expect( page.locator("tr[data-name='sharemaprowtemplate']:not(.hidden)") @@ -372,12 +375,31 @@ def test_incoming_shares(page: Page, radicale_server: str) -> None: ) ).to_be_checked() - # 6. Assert no error was shown - expect(page.locator('#incomingsharingscene span[data-name="error"]')).to_be_hidden() - + # 6. Verify "shared by admin" and button visibility in the collection article page.click('#incomingsharingscene button[data-name="cancel"]') expect(page.locator("#incomingsharingscene")).to_be_hidden() + article = page.locator("article:not(.hidden)").first + expect(article.locator('[data-name="shared-by"]')).to_be_visible() + expect(article.locator('[data-name="shared-by-owner"]')).to_have_text("admin") + + # Action buttons are only visible on mouseover + article.hover() + + # Share and delete buttons should be hidden for all incoming shares + expect(article.locator('a[data-name="share"]')).to_be_hidden() + expect(article.locator('[data-name="shareoption"]')).to_be_hidden() + expect(article.locator('a[data-name="delete"]')).to_be_hidden() + + # Edit button depends on permissions + if permissions == "rw": + expect(article.locator('a[data-name="edit"]')).to_be_visible() + else: + expect(article.locator('a[data-name="edit"]')).to_be_hidden() + + # 7. Assert no error was shown + expect(page.locator('#incomingsharingscene span[data-name="error"]')).to_be_hidden() + def test_no_incoming_shares_message(page: Page, radicale_server: str) -> None: # 1. Max logs in diff --git a/radicale/web/internal_data/index.html b/radicale/web/internal_data/index.html index 02d32749..a21866b0 100644 --- a/radicale/web/internal_data/index.html +++ b/radicale/web/internal_data/index.html @@ -74,6 +74,7 @@

Title

+ Address book Calendar, journal and tasks Calendar and journal diff --git a/radicale/web/internal_data/js/scenes/CollectionsScene.js b/radicale/web/internal_data/js/scenes/CollectionsScene.js index 2ccb3b6d..a8467449 100644 --- a/radicale/web/internal_data/js/scenes/CollectionsScene.js +++ b/radicale/web/internal_data/js/scenes/CollectionsScene.js @@ -121,8 +121,9 @@ export class CollectionsScene { /** * @param {any[]} collections + * @param {import("../api/sharing.js").Share[]} shares */ - function show_collections(collections) { + function show_collections(collections, shares) { /** @type {HTMLElement} */ let navBar = document.querySelector("#logoutview"); let heightOfNavBar = navBar.offsetHeight + "px"; html_scene.style.marginTop = heightOfNavBar; @@ -164,6 +165,23 @@ export class CollectionsScene { node.querySelector("[data-name=" + e + "]").classList.add("hidden"); } }); + let share_info = node.querySelector("[data-name=shared-by]"); + let share = (shares || []).find(s => s.ShareType === "map" && (s.PathOrToken || "").replace(/\/+$/, "") === (collection.href || "").replace(/\/+$/, "")); + if (share && share.Owner !== user) { + share_info.classList.remove("hidden"); + node.querySelector("[data-name=shared-by-owner]").textContent = share.Owner; + let share_option = node.querySelector("[data-name=shareoption]"); + if (share_option) { + share_option.classList.add("hidden"); + share_option.removeAttribute("data-name"); + } + delete_btn.classList.add("hidden"); + if (!/w/i.test(share.Permissions || "")) { + edit_btn.classList.add("hidden"); + } else { + edit_btn.classList.remove("hidden"); + } + } title_form.textContent = collection.displayname || collection.href; if (title_form.textContent.length > 30) { title_form.classList.add("smalltext");