diff --git a/integ_tests/common.py b/integ_tests/common.py
index aedc13be..d55af761 100644
--- a/integ_tests/common.py
+++ b/integ_tests/common.py
@@ -45,6 +45,8 @@ permit_create_map = true
with open(user_path, "w") as f:
f.write(
"""admin:adminpassword
+max:maxpassword
+
"""
)
diff --git a/integ_tests/test_sharing.py b/integ_tests/test_sharing.py
index 9a97f9de..4cb26b88 100644
--- a/integ_tests/test_sharing.py
+++ b/integ_tests/test_sharing.py
@@ -49,3 +49,46 @@ def test_create_and_delete_share_by_key(page: Page, radicale_server: str) -> Non
expect(
page.locator("tr[data-name='sharetokenrowtemplate']:not(.hidden)")
).to_have_count(0)
+
+
+def test_create_and_delete_share_by_map(page: Page, radicale_server: str) -> None:
+ login(page, radicale_server)
+ create_collection(page, radicale_server)
+ page.hover("article:not(.hidden)")
+ page.click('article:not(.hidden) a[data-name="share"]', force=True, strict=True)
+
+ expect(
+ page.locator("tr[data-name='sharemaprowtemplate']:not(.hidden)")
+ ).to_have_count(0)
+
+ page.click('button[data-name="sharebymap"]')
+ page.locator('input[data-name="shareuser"]').fill("max")
+ page.locator('input[data-name="sharehref"]').fill("1234")
+ page.click('#newshare button[data-name="submit"]')
+ expect(
+ page.locator("tr[data-name='sharemaprowtemplate']:not(.hidden)")
+ ).to_have_count(1)
+ expect(
+ page.locator("tr[data-name='sharemaprowtemplate']:not(.hidden) img[alt='RO']")
+ ).to_be_visible()
+ page.once("dialog", lambda dialog: dialog.accept())
+ page.click('tr:not(.hidden) button[data-name="delete"]', strict=True)
+ expect(
+ page.locator("tr[data-name='sharemaprowtemplate']:not(.hidden)")
+ ).to_have_count(0)
+ page.click('button[data-name="sharebymap"]')
+ page.click('label[for="newshare_attr_permissions_rw"]')
+ page.locator('input[data-name="shareuser"]').fill("max")
+ page.locator('input[data-name="sharehref"]').fill("1234")
+ page.click('#newshare button[data-name="submit"]')
+ expect(
+ page.locator("tr[data-name='sharemaprowtemplate']:not(.hidden)")
+ ).to_have_count(1)
+ expect(
+ page.locator("tr[data-name='sharemaprowtemplate']:not(.hidden) img[alt='RW']")
+ ).to_be_visible()
+ page.once("dialog", lambda dialog: dialog.accept())
+ page.click('tr:not(.hidden) button[data-name="delete"]', strict=True)
+ expect(
+ page.locator("tr[data-name='sharemaprowtemplate']:not(.hidden)")
+ ).to_have_count(0)
diff --git a/radicale/web/internal_data/NewShareScene.js b/radicale/web/internal_data/NewShareScene.js
index e0f48afc..b9819a21 100644
--- a/radicale/web/internal_data/NewShareScene.js
+++ b/radicale/web/internal_data/NewShareScene.js
@@ -18,7 +18,7 @@
* along with this program. If not, see .
*/
-import { add_share_by_token } from "./api.js";
+import { add_share_by_map, add_share_by_token } from "./api.js";
import { Scene, pop_scene, scene_stack } from "./scene_manager.js";
/**
@@ -29,11 +29,15 @@ export class NewShareScene {
* @param {string} user
* @param {string} password
* @param {string} pathMapped
+ * @param {string} shareType
* @param {function():void} onclose
*/
- constructor(user, password, pathMapped, onclose) {
+ constructor(user, password, pathMapped, shareType, onclose) {
/** @type {HTMLElement} */ let html_scene = document.getElementById("newshare");
/** @type {HTMLFormElement} */ let form = html_scene.querySelector("form");
+ /** @type {HTMLElement} */ let sharemapfields = html_scene.querySelector("[data-name=sharemapfields]");
+ /** @type {HTMLInputElement} */ let shareuser_input = html_scene.querySelector("[data-name=shareuser]");
+ /** @type {HTMLInputElement} */ let sharehref_input = html_scene.querySelector("[data-name=sharehref]");
/** @type {HTMLInputElement} */ let enabled_checkbox = html_scene.querySelector("[data-name=enabled]");
/** @type {HTMLInputElement} */ let hidden_checkbox = html_scene.querySelector("[data-name=hidden]");
let permissions_ro_radio = /** @type {HTMLInputElement} */ (document.getElementById("newshare_attr_permissions_ro"));
@@ -62,12 +66,20 @@ export class NewShareScene {
let permissions = permissions_rw_radio.checked ? "rw" : "r";
let properties = properties_input.value;
- add_share_by_token(user, password, pathMapped, permissions, enabled, hidden, properties, function () {
+ let callback = function () {
if (scene_index !== null) {
pop_scene(scene_index - 1);
}
if (onclose) onclose();
- });
+ };
+
+ if (shareType === "map") {
+ let share_user = shareuser_input.value;
+ let href = sharehref_input.value;
+ add_share_by_map(user, password, pathMapped, permissions, enabled, hidden, properties, share_user, href, callback);
+ } else {
+ add_share_by_token(user, password, pathMapped, permissions, enabled, hidden, properties, callback);
+ }
} catch (err) {
console.error(err);
}
@@ -81,6 +93,14 @@ export class NewShareScene {
cancel_btn.onclick = oncancel;
form.onsubmit = onsubmit;
+ if (shareType === "map") {
+ sharemapfields.classList.remove("hidden");
+ } else {
+ sharemapfields.classList.add("hidden");
+ }
+
+ shareuser_input.value = "";
+ sharehref_input.value = "";
enabled_checkbox.checked = true;
hidden_checkbox.checked = false;
permissions_ro_radio.checked = true;
diff --git a/radicale/web/internal_data/ShareCollectionScene.js b/radicale/web/internal_data/ShareCollectionScene.js
index 34f11c63..834c7a41 100644
--- a/radicale/web/internal_data/ShareCollectionScene.js
+++ b/radicale/web/internal_data/ShareCollectionScene.js
@@ -45,6 +45,9 @@ export class ShareCollectionScene {
/** @type {HTMLElement} */ let cancel_btn = html_scene.querySelector("[data-name=cancel]");
/** @type {HTMLElement} */ let share_by_token_btn = html_scene.querySelector(
"button[data-name=sharebytoken]"
+ );
+ /** @type {HTMLElement} */ let share_by_map_btn = html_scene.querySelector(
+ "button[data-name=sharebymap]"
);
/** @type {HTMLElement} */ let share_by_token_div = html_scene.querySelector(
"div[data-name=sharebytoken]"
@@ -65,7 +68,14 @@ export class ShareCollectionScene {
}
function onsharebytoken() {
- let new_share_scene = new NewShareScene(user, password, collection.href, function () {
+ let new_share_scene = new NewShareScene(user, password, collection.href, "token", function () {
+ update_share_list(user, password, collection);
+ });
+ push_scene(new_share_scene, false);
+ }
+
+ function onsharebymap() {
+ let new_share_scene = new NewShareScene(user, password, collection.href, "map", function () {
update_share_list(user, password, collection);
});
push_scene(new_share_scene, false);
@@ -91,6 +101,15 @@ export class ShareCollectionScene {
if (share_by_token_div) share_by_token_div.classList.add("hidden");
}
+ if (server_features.sharing && server_features.sharing.PermittedCreateCollectionByMap) {
+ if (share_by_map_btn) {
+ share_by_map_btn.classList.remove("hidden");
+ share_by_map_btn.onclick = onsharebymap;
+ }
+ } else {
+ if (share_by_map_btn) share_by_map_btn.classList.add("hidden");
+ }
+
if (server_features.sharing && server_features.sharing.FeatureEnabledCollectionByMap) {
if (share_by_map_div) share_by_map_div.classList.remove("hidden");
} else {
diff --git a/radicale/web/internal_data/api.js b/radicale/web/internal_data/api.js
index 878ec396..8b1f1cf9 100644
--- a/radicale/web/internal_data/api.js
+++ b/radicale/web/internal_data/api.js
@@ -519,6 +519,54 @@ export function add_share_by_token(
);
}
+/**
+ * @param {string} user
+ * @param {string} password
+ * @param {string} pathMapped
+ * @param {string} permissions
+ * @param {boolean} enabled
+ * @param {boolean} hidden
+ * @param {string} properties
+ * @param {string} share_user
+ * @param {string} href
+ * @param {function():void} callback
+ */
+export function add_share_by_map(
+ user,
+ password,
+ pathMapped,
+ permissions,
+ enabled,
+ hidden,
+ properties,
+ share_user,
+ href,
+ callback,
+) {
+ call_sharing_api(
+ user,
+ password,
+ "map/create",
+ {
+ PathMapped: pathMapped,
+ Permissions: permissions,
+ Enabled: enabled,
+ Hidden: hidden,
+ Properties: properties,
+ User: share_user,
+ PathOrToken: "/" + share_user + "/" + href,
+ },
+ function (response) {
+ let json_response = JSON.parse(response);
+ if (json_response["Status"] !== "success") {
+ console.error("Failed to create share map: " + (json_response["Status"] || "Unknown error"));
+ } else {
+ callback();
+ }
+ },
+ );
+}
+
/**
* @param {string} user
* @param {string} password
diff --git a/radicale/web/internal_data/css/main.css b/radicale/web/internal_data/css/main.css
index 5dc06481..7ba2e92b 100644
--- a/radicale/web/internal_data/css/main.css
+++ b/radicale/web/internal_data/css/main.css
@@ -263,6 +263,11 @@ main {
margin-top: 15px;
}
+#newshare input[type=text] {
+ margin-bottom: 0 !important;
+}
+
+
.deleteconfirmationtxt {
text-align: center;
font-size: 1em;
diff --git a/radicale/web/internal_data/index.html b/radicale/web/internal_data/index.html
index 0d1c6c82..43fe4ac0 100644
--- a/radicale/web/internal_data/index.html
+++ b/radicale/web/internal_data/index.html
@@ -185,16 +185,15 @@
|
-
+
+ |
+ |
+
+
+ |
+
@@ -206,6 +205,13 @@