Skip to content

Commit

Permalink
Merge pull request #1432 from MatthewHana/v3.2-devel
Browse files Browse the repository at this point in the history
WEB UI: New WebUI Improvements

Thank you very much!
  • Loading branch information
pbiering authored Mar 13, 2024
2 parents 3c81f43 + c128b0d commit 74be616
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 6 deletions.
11 changes: 11 additions & 0 deletions radicale/web/internal_data/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,11 @@ main{
text-align: center;
}

#collectionsscene article small[data-name=contentcount]{
font-weight: bold;
font-style: normal;
}

#editcollectionscene p span{
word-wrap:break-word;
font-weight: bold;
Expand Down Expand Up @@ -228,6 +233,12 @@ main{
margin-top: 15px;
}

.deleteconfirmationtxt{
text-align: center;
font-size: 1em;
font-weight: bold;
}

.fabcontainer{
display: flex;
flex-direction: column-reverse;
Expand Down
103 changes: 98 additions & 5 deletions radicale/web/internal_data/fn.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ const ROOT_PATH = location.pathname.replace(new RegExp("/+[^/]+/*(/index\\.html?
*/
const COLOR_RE = new RegExp("^(#[0-9A-Fa-f]{6})(?:[0-9A-Fa-f]{2})?$");


/**
* The text needed to confirm deleting a collection
* @const
*/
const DELETE_CONFIRMATION_TEXT = "DELETE";

/**
* Escape string for usage in XML
* @param {string} s
Expand Down Expand Up @@ -94,6 +101,23 @@ const CollectionType = {
union.push(this.WEBCAL);
}
return union.join("_");
},
valid_options_for_type: function(a){
a = a.trim().toUpperCase();
switch(a){
case CollectionType.CALENDAR_JOURNAL_TASKS:
case CollectionType.CALENDAR_JOURNAL:
case CollectionType.CALENDAR_TASKS:
case CollectionType.JOURNAL_TASKS:
case CollectionType.CALENDAR:
case CollectionType.JOURNAL:
case CollectionType.TASKS:
return [CollectionType.CALENDAR_JOURNAL_TASKS, CollectionType.CALENDAR_JOURNAL, CollectionType.CALENDAR_TASKS, CollectionType.JOURNAL_TASKS, CollectionType.CALENDAR, CollectionType.JOURNAL, CollectionType.TASKS];
case CollectionType.ADDRESSBOOK:
case CollectionType.WEBCAL:
default:
return [a];
}
}
};

Expand All @@ -106,13 +130,14 @@ const CollectionType = {
* @param {string} description
* @param {string} color
*/
function Collection(href, type, displayname, description, color, source) {
function Collection(href, type, displayname, description, color, contentcount, source) {
this.href = href;
this.type = type;
this.displayname = displayname;
this.color = color;
this.description = description;
this.source = source;
this.contentcount = contentcount;
}

/**
Expand All @@ -139,6 +164,7 @@ function get_principal(user, password, callback) {
CollectionType.PRINCIPAL,
displayname_element ? displayname_element.textContent : "",
"",
0,
""), null);
} else {
callback(null, "Internal error");
Expand Down Expand Up @@ -188,6 +214,7 @@ function get_collections(user, password, collection, callback) {
let addressbookcolor_element = response.querySelector(response_query + " > *|propstat > *|prop > *|addressbook-color");
let calendardesc_element = response.querySelector(response_query + " > *|propstat > *|prop > *|calendar-description");
let addressbookdesc_element = response.querySelector(response_query + " > *|propstat > *|prop > *|addressbook-description");
let contentcount_element = response.querySelector(response_query + " > *|propstat > *|prop > *|getcontentcount");
let webcalsource_element = response.querySelector(response_query + " > *|propstat > *|prop > *|source");
let components_query = response_query + " > *|propstat > *|prop > *|supported-calendar-component-set";
let components_element = response.querySelector(components_query);
Expand All @@ -197,11 +224,13 @@ function get_collections(user, password, collection, callback) {
let color = "";
let description = "";
let source = "";
let count = 0;
if (resourcetype_element) {
if (resourcetype_element.querySelector(resourcetype_query + " > *|addressbook")) {
type = CollectionType.ADDRESSBOOK;
color = addressbookcolor_element ? addressbookcolor_element.textContent : "";
description = addressbookdesc_element ? addressbookdesc_element.textContent : "";
count = contentcount_element ? parseInt(contentcount_element.textContent) : 0;
} else if (resourcetype_element.querySelector(resourcetype_query + " > *|subscribed")) {
type = CollectionType.WEBCAL;
source = webcalsource_element ? webcalsource_element.textContent : "";
Expand All @@ -221,6 +250,7 @@ function get_collections(user, password, collection, callback) {
}
color = calendarcolor_element ? calendarcolor_element.textContent : "";
description = calendardesc_element ? calendardesc_element.textContent : "";
count = contentcount_element ? parseInt(contentcount_element.textContent) : 0;
}
}
let sane_color = color.trim();
Expand All @@ -233,7 +263,7 @@ function get_collections(user, password, collection, callback) {
}
}
if (href.substr(-1) === "/" && href !== collection.href && type) {
collections.push(new Collection(href, type, displayname, description, sane_color, source));
collections.push(new Collection(href, type, displayname, description, sane_color, count, source));
}
}
collections.sort(function(a, b) {
Expand Down Expand Up @@ -265,6 +295,7 @@ function get_collections(user, password, collection, callback) {
'<C:supported-calendar-component-set />' +
'<CR:addressbook-description />' +
'<CS:source />' +
'<RADICALE:getcontentcount />' +
'</prop>' +
'</propfind>');
return request;
Expand Down Expand Up @@ -708,6 +739,7 @@ function CollectionsScene(user, password, collection, onerror) {
node.classList.remove("hidden");
let title_form = node.querySelector("[data-name=title]");
let description_form = node.querySelector("[data-name=description]");
let contentcount_form = node.querySelector("[data-name=contentcount]");
let url_form = node.querySelector("[data-name=url]");
let color_form = node.querySelector("[data-name=color]");
let delete_btn = node.querySelector("[data-name=delete]");
Expand Down Expand Up @@ -739,6 +771,9 @@ function CollectionsScene(user, password, collection, onerror) {
if(description_form.textContent.length > 150){
description_form.classList.add("smalltext");
}
if(collection.type != CollectionType.WEBCAL){
contentcount_form.textContent = (collection.contentcount > 0 ? collection.contentcount : "No") + " item" + (collection.contentcount == 1 ? "" : "s") + " in collection";
}
let href = SERVER + collection.href;
url_form.value = href;
download_btn.href = href;
Expand Down Expand Up @@ -939,14 +974,25 @@ function DeleteCollectionScene(user, password, collection) {
let html_scene = document.getElementById("deletecollectionscene");
let title_form = html_scene.querySelector("[data-name=title]");
let error_form = html_scene.querySelector("[data-name=error]");
let confirmation_txt = html_scene.querySelector("[data-name=confirmationtxt]");
let delete_confirmation_lbl = html_scene.querySelector("[data-name=deleteconfirmationtext]");
let delete_btn = html_scene.querySelector("[data-name=delete]");
let cancel_btn = html_scene.querySelector("[data-name=cancel]");

delete_confirmation_lbl.innerHTML = DELETE_CONFIRMATION_TEXT;
confirmation_txt.value = "";
confirmation_txt.addEventListener("keydown", onkeydown);

/** @type {?number} */ let scene_index = null;
/** @type {?XMLHttpRequest} */ let delete_req = null;
let error = "";

function ondelete() {
let confirmation_text_value = confirmation_txt.value;
if(confirmation_text_value != DELETE_CONFIRMATION_TEXT){
alert("Please type the confirmation text to delete this collection.");
return;
}
try {
let loading_scene = new LoadingScene();
push_scene(loading_scene);
Expand Down Expand Up @@ -977,6 +1023,13 @@ function DeleteCollectionScene(user, password, collection) {
return false;
}

function onkeydown(event){
if (event.keyCode !== 13) {
return;
}
ondelete();
}

this.show = function() {
this.release();
scene_index = scene_stack.length - 1;
Expand Down Expand Up @@ -1031,6 +1084,8 @@ function CreateEditCollectionScene(user, password, collection) {
let html_scene = document.getElementById(edit ? "editcollectionscene" : "createcollectionscene");
let title_form = edit ? html_scene.querySelector("[data-name=title]") : null;
let error_form = html_scene.querySelector("[data-name=error]");
let href_form = html_scene.querySelector("[data-name=href]");
let href_label = html_scene.querySelector("label[for=href]");
let displayname_form = html_scene.querySelector("[data-name=displayname]");
let displayname_label = html_scene.querySelector("label[for=displayname]");
let description_form = html_scene.querySelector("[data-name=description]");
Expand All @@ -1057,28 +1112,46 @@ function CreateEditCollectionScene(user, password, collection) {
let type = edit ? collection.type : CollectionType.CALENDAR_JOURNAL_TASKS;
let color = edit && collection.color ? collection.color : "#" + random_hex(6);

if(!edit){
href_form.addEventListener("keydown", cleanHREFinput);
}

function remove_invalid_types() {
if (!edit) {
return;
}
/** @type {HTMLOptionsCollection} */ let options = type_form.options;
// remove all options that are not supersets
let valid_type_options = CollectionType.valid_options_for_type(type);
for (let i = options.length - 1; i >= 0; i--) {
if (!CollectionType.is_subset(type, options[i].value)) {
if (valid_type_options.indexOf(options[i].value) < 0) {
options.remove(i);
}
}
}

function read_form() {
if(!edit){
cleanHREFinput();
let newhreftxtvalue = href_form.value.trim().toLowerCase();
if(!isValidHREF(newhreftxtvalue)){
alert("You must enter a valid HREF");
return false;
}
href = collection.href + "/" + newhreftxtvalue + "/";
}
displayname = displayname_form.value;
description = description_form.value;
source = source_form.value;
type = type_form.value;
color = color_form.value;
return true;
}

function fill_form() {
if(!edit){
href_form.value = random_uuid();
}
displayname_form.value = displayname;
description_form.value = description;
source_form.value = source;
Expand All @@ -1095,7 +1168,9 @@ function CreateEditCollectionScene(user, password, collection) {

function onsubmit() {
try {
read_form();
if(!read_form()){
return false;
}
let sane_color = color.trim();
if (sane_color) {
let color_match = COLOR_RE.exec(sane_color);
Expand All @@ -1108,7 +1183,7 @@ function CreateEditCollectionScene(user, password, collection) {
}
let loading_scene = new LoadingScene();
push_scene(loading_scene);
let collection = new Collection(href, type, displayname, description, sane_color, source);
let collection = new Collection(href, type, displayname, description, sane_color, 0, source);
let callback = function(error1) {
if (scene_index === null) {
return;
Expand Down Expand Up @@ -1141,6 +1216,13 @@ function CreateEditCollectionScene(user, password, collection) {
return false;
}

function cleanHREFinput(event){
let currentTxtVal = href_form.value.trim().toLowerCase();
//Clean the HREF to remove non lowercase letters and dashes
currentTxtVal = currentTxtVal.replace(/(?![0-9a-z\-\_])./g, '');
href_form.value = currentTxtVal;
}

function onTypeChange(e){
if(type_form.value == CollectionType.WEBCAL){
source_label.classList.remove("hidden");
Expand All @@ -1151,6 +1233,17 @@ function CreateEditCollectionScene(user, password, collection) {
}
}

function isValidHREF(href){
if(href.length < 1){
return false;
}
if(href.indexOf("/") != -1){
return false;
}

return true;
}

this.show = function() {
this.release();
scene_index = scene_stack.length - 1;
Expand Down
6 changes: 5 additions & 1 deletion radicale/web/internal_data/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ <h3 class="title" data-name="title">Title</h3>
<span data-name="TASKS">Tasks</span>
<span data-name="WEBCAL">Webcal</span>
</small>
<small data-name="contentcount"></small>
<input type="text" data-name="url" value="" readonly="" onfocus="this.setSelectionRange(0, 99999);">
<p data-name="description" style="word-wrap:break-word;">Description</p>
<ul>
Expand Down Expand Up @@ -131,6 +132,8 @@ <h1>Create a new Collection</h1>
<option value="TASKS">Tasks</option>
<option value="WEBCAL">Webcal</option>
</select>
<label for="href">HREF:</label>
<input data-name="href" type="text">
<label for="displayname">Title:</label>
<input data-name="displayname" type="text">
<label for="description">Description:</label>
Expand Down Expand Up @@ -164,7 +167,8 @@ <h1>Upload Collection</h1>

<section id="deletecollectionscene" class="container hidden">
<h1>Delete Collection</h1>
<p>Do you want to delete the collection <span class="title" data-name="title">title</span>? </p>
<p>To delete the collection <span class="title" data-name="title">title</span> please enter the phrase <strong data-name="deleteconfirmationtext"></strong> in the box below:</p>
<input type="text" class="deleteconfirmationtxt" data-name="confirmationtxt" />
<p class="red">WARNING: This action cannot be reversed.</p>
<form>
<button type="button" class="red" data-name="delete">Delete</button>
Expand Down

0 comments on commit 74be616

Please sign in to comment.