Implemented proper property overrides

This commit is contained in:
Max Berger
2026-03-10 22:35:26 +01:00
parent 6f4dbd10e4
commit d513efeaac
6 changed files with 150 additions and 12 deletions

View File

@@ -59,6 +59,7 @@ collection_by_map = true
collection_by_token = true
permit_create_token = true
permit_create_map = true
permit_properties_overlay = true
"""
)

View File

@@ -112,3 +112,61 @@ def test_create_and_delete_share_by_map(page: Page, radicale_server: str) -> Non
expect(
page.locator("tr[data-name='sharemaprowtemplate']:not(.hidden)")
).to_have_count(0)
def test_share_with_property_overrides(page: Page, radicale_server: str) -> None:
login(page, radicale_server)
# Create a collection with specific details
page.click('a[data-name="new"]')
page.locator('#createcollectionscene input[data-name="displayname"]').fill("Test Collection")
page.locator('#createcollectionscene input[data-name="description"]').fill("Original Description")
page.locator('#createcollectionscene input[data-name="color"]').fill("#ff0000")
page.click('#createcollectionscene button[data-name="submit"]')
page.hover("article:not(.hidden)")
page.click('article:not(.hidden) a[data-name="share"]', force=True, strict=True)
page.click('button[data-name="sharebytoken"]')
# Verify defaults
expect(page.locator('input[data-name="description_override"]')).to_have_value("Original Description")
expect(page.locator('input[data-name="color_override"]')).to_have_value("#ff0000")
expect(page.locator('input[data-name="description_override"]')).to_be_disabled()
expect(page.locator('input[data-name="color_override"]')).to_be_disabled()
# Set overrides
page.click('label[for="newshare_attr_description_enabled"]')
page.locator('input[data-name="description_override"]').fill("Overridden Description")
page.click('label[for="newshare_attr_color_enabled"]')
page.locator('input[data-name="color_override"]').fill("#00ff00")
page.click('#newshare button[data-name="submit"]')
# Verify the share was created
expect(
page.locator("tr[data-name='sharetokenrowtemplate']:not(.hidden)")
).to_have_count(1)
def test_share_journal_no_overrides(page: Page, radicale_server: str) -> None:
login(page, radicale_server)
# Create a collection of type JOURNAL
page.click('a[data-name="new"]')
page.locator('#createcollectionscene select[data-name="type"]').select_option("JOURNAL")
page.locator('#createcollectionscene input[data-name="displayname"]').fill("Test Journal")
page.locator('#createcollectionscene input[data-name="description"]').fill("Journal Description")
page.click('#createcollectionscene button[data-name="submit"]')
page.hover("article:not(.hidden)")
page.click('article:not(.hidden) a[data-name="share"]', force=True, strict=True)
page.click('button[data-name="sharebytoken"]')
# Verify property override fieldset is hidden
expect(page.locator('fieldset[data-name="properties_override"]')).to_be_hidden()
# Create the share
page.click('#newshare button[data-name="submit"]')
# Verify the share was created
expect(
page.locator("tr[data-name='sharetokenrowtemplate']:not(.hidden)")
).to_have_count(1)

View File

@@ -227,9 +227,18 @@
<input type="radio" data-name="permissions" checked="false" id="newshare_attr_permissions_rw"
name="newshare_permissions" /><label for="newshare_attr_permissions_rw">Read/Write</label>
</fieldset>
<fieldset>
<fieldset data-name="properties_override">
<legend>Properties override</legend>
<input type="text" data-name="properties" />
<div class="property-override">
<input type="checkbox" data-name="description_override_enabled" id="newshare_attr_description_enabled">
<label for="newshare_attr_description_enabled">Description:</label>
<input type="text" data-name="description_override" disabled>
</div>
<div class="property-override">
<input type="checkbox" data-name="color_override_enabled" id="newshare_attr_color_enabled">
<label for="newshare_attr_color_enabled">Color:</label>
<input type="color" data-name="color_override" disabled>
</div>
</fieldset>
<button type="submit" class="green" data-name="submit">Create</button>
<button type="button" class="red" data-name="cancel">Cancel</button>

View File

@@ -483,6 +483,36 @@ export function reload_sharing_list(user, password, collection, callback) {
);
}
/**
* Property keys for different collection types.
* Map to OVERLAY_PROPERTIES_WHITELIST in radicale/sharing/__init__.py
*/
export const OVERLAY_PROPERTIES = {
CALENDAR: {
DESCRIPTION: "C:calendar-description",
COLOR: "ICAL:calendar-color",
},
ADDRESSBOOK: {
DESCRIPTION: "CR:addressbook-description",
COLOR: "INF:addressbook-color",
}
};
/**
* Returns the correct internal property key for a given collection type and property name.
* @param {string} type Collection type (ADDRESSBOOK, CALENDAR, etc.)
* @param {"DESCRIPTION" | "COLOR"} property Property name
* @returns {string | null} Internal property key or null if not supported
*/
export function get_property_key(type, property) {
if (type === CollectionType.ADDRESSBOOK) {
return OVERLAY_PROPERTIES.ADDRESSBOOK[property];
} else if (CollectionType.is_subset(CollectionType.CALENDAR, type)) {
return OVERLAY_PROPERTIES.CALENDAR[property];
}
return null;
}
/**
* @param {string} user
* @param {string} password
@@ -490,7 +520,7 @@ export function reload_sharing_list(user, password, collection, callback) {
* @param {string} permissions
* @param {boolean} enabled
* @param {boolean} hidden
* @param {string} properties
* @param {Object} properties
* @param {function(?string):void} callback
*/
export function add_share_by_token(
@@ -536,7 +566,7 @@ export function add_share_by_token(
* @param {string} permissions
* @param {boolean} enabled
* @param {boolean} hidden
* @param {string} properties
* @param {Object} properties
* @param {string} share_user
* @param {string} href
* @param {function(?string):void} callback

View File

@@ -19,7 +19,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { add_share_by_map, add_share_by_token } from "../api/api.js";
import { add_share_by_map, add_share_by_token, get_property_key } from "../api/api.js";
import { CollectionType } from "../models/collection.js";
import { ErrorHandler } from "../utils/error.js";
import { FormValidator, validate_href, validate_non_empty } from "../utils/form_validator.js";
import { onCleanHREFinput, random_uuid } from "../utils/misc.js";
@@ -32,11 +33,12 @@ export class NewShareScene {
/**
* @param {string} user
* @param {string} password
* @param {string} pathMapped
* @param {import("../models/collection.js").Collection} collection
* @param {string} shareType
* @param {function():void} onclose
*/
constructor(user, password, pathMapped, shareType, onclose) {
constructor(user, password, collection, shareType, onclose) {
let pathMapped = collection.href;
/** @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]");
@@ -46,7 +48,13 @@ export class NewShareScene {
/** @type {HTMLInputElement} */ let hidden_checkbox = html_scene.querySelector("[data-name=hidden]");
let permissions_ro_radio = /** @type {HTMLInputElement} */ (document.getElementById("newshare_attr_permissions_ro"));
let permissions_rw_radio = /** @type {HTMLInputElement} */ (document.getElementById("newshare_attr_permissions_rw"));
/** @type {HTMLInputElement} */ let properties_input = html_scene.querySelector("[data-name=properties]");
/** @type {HTMLElement} */ let properties_fieldset = html_scene.querySelector("[data-name=properties_override]");
/** @type {HTMLInputElement} */ let description_override_enabled = html_scene.querySelector("[data-name=description_override_enabled]");
/** @type {HTMLInputElement} */ let description_override_input = html_scene.querySelector("[data-name=description_override]");
/** @type {HTMLInputElement} */ let color_override_enabled = html_scene.querySelector("[data-name=color_override_enabled]");
/** @type {HTMLInputElement} */ let color_override_input = html_scene.querySelector("[data-name=color_override]");
/** @type {HTMLElement} */ let error_form = html_scene.querySelector("[data-name=error]");
/** @type {HTMLElement} */ let cancel_btn = html_scene.querySelector("[data-name=cancel]");
@@ -58,6 +66,13 @@ export class NewShareScene {
sharehref_input.addEventListener("input", onCleanHREFinput);
description_override_enabled.onchange = function () {
description_override_input.disabled = !description_override_enabled.checked;
};
color_override_enabled.onchange = function () {
color_override_input.disabled = !color_override_enabled.checked;
};
/** @type {?number} */ let scene_index = null;
function oncancel() {
@@ -82,7 +97,16 @@ export class NewShareScene {
let enabled = enabled_checkbox.checked;
let hidden = hidden_checkbox.checked;
let permissions = permissions_rw_radio.checked ? "rw" : "r";
let properties = properties_input.value;
let properties = {};
if (description_override_enabled.checked) {
let key = get_property_key(collection.type, "DESCRIPTION");
if (key) properties[key] = description_override_input.value;
}
if (color_override_enabled.checked) {
let key = get_property_key(collection.type, "COLOR");
if (key) properties[key] = color_override_input.value + (color_override_input.value ? "ff" : "");
}
let callback = function (/** @type {string} */ error) {
if (scene_index === null) {
@@ -122,7 +146,23 @@ export class NewShareScene {
hidden_checkbox.checked = false;
permissions_ro_radio.checked = true;
permissions_rw_radio.checked = false;
properties_input.value = "";
description_override_enabled.checked = false;
description_override_input.value = collection.description || "";
description_override_input.disabled = true;
color_override_enabled.checked = false;
color_override_input.value = collection.color || "#ffffff";
color_override_input.disabled = true;
let is_calendar = CollectionType.is_subset(CollectionType.CALENDAR, collection.type);
let is_addressbook = collection.type === CollectionType.ADDRESSBOOK;
if (is_calendar || is_addressbook) {
properties_fieldset.classList.remove("hidden");
} else {
properties_fieldset.classList.add("hidden");
}
if (shareType === "map") {
sharehref_input.value = random_uuid();
sharemapfields.classList.remove("hidden");

View File

@@ -73,14 +73,14 @@ export class ShareCollectionScene {
}
function onsharebytoken() {
let new_share_scene = new NewShareScene(user, password, collection.href, "token", function () {
let new_share_scene = new NewShareScene(user, password, collection, "token", function () {
update_share_list(user, password, collection, errorHandler);
});
push_scene(new_share_scene, false);
}
function onsharebymap() {
let new_share_scene = new NewShareScene(user, password, collection.href, "map", function () {
let new_share_scene = new NewShareScene(user, password, collection, "map", function () {
update_share_list(user, password, collection, errorHandler);
});
push_scene(new_share_scene, false);