@@ -36,6 +36,13 @@ const ROOT_PATH = location.pathname.replace(new RegExp("/+[^/]+/*(/index\\.html?
36
36
*/
37
37
const COLOR_RE = new RegExp ( "^(#[0-9A-Fa-f]{6})(?:[0-9A-Fa-f]{2})?$" ) ;
38
38
39
+
40
+ /**
41
+ * The text needed to confirm deleting a collection
42
+ * @const
43
+ */
44
+ const DELETE_CONFIRMATION_TEXT = "DELETE" ;
45
+
39
46
/**
40
47
* Escape string for usage in XML
41
48
* @param {string } s
@@ -94,6 +101,23 @@ const CollectionType = {
94
101
union . push ( this . WEBCAL ) ;
95
102
}
96
103
return union . join ( "_" ) ;
104
+ } ,
105
+ valid_options_for_type : function ( a ) {
106
+ a = a . trim ( ) . toUpperCase ( ) ;
107
+ switch ( a ) {
108
+ case CollectionType . CALENDAR_JOURNAL_TASKS :
109
+ case CollectionType . CALENDAR_JOURNAL :
110
+ case CollectionType . CALENDAR_TASKS :
111
+ case CollectionType . JOURNAL_TASKS :
112
+ case CollectionType . CALENDAR :
113
+ case CollectionType . JOURNAL :
114
+ case CollectionType . TASKS :
115
+ return [ CollectionType . CALENDAR_JOURNAL_TASKS , CollectionType . CALENDAR_JOURNAL , CollectionType . CALENDAR_TASKS , CollectionType . JOURNAL_TASKS , CollectionType . CALENDAR , CollectionType . JOURNAL , CollectionType . TASKS ] ;
116
+ case CollectionType . ADDRESSBOOK :
117
+ case CollectionType . WEBCAL :
118
+ default :
119
+ return [ a ] ;
120
+ }
97
121
}
98
122
} ;
99
123
@@ -106,13 +130,14 @@ const CollectionType = {
106
130
* @param {string } description
107
131
* @param {string } color
108
132
*/
109
- function Collection ( href , type , displayname , description , color , source ) {
133
+ function Collection ( href , type , displayname , description , color , contentcount , source ) {
110
134
this . href = href ;
111
135
this . type = type ;
112
136
this . displayname = displayname ;
113
137
this . color = color ;
114
138
this . description = description ;
115
139
this . source = source ;
140
+ this . contentcount = contentcount ;
116
141
}
117
142
118
143
/**
@@ -139,6 +164,7 @@ function get_principal(user, password, callback) {
139
164
CollectionType . PRINCIPAL ,
140
165
displayname_element ? displayname_element . textContent : "" ,
141
166
"" ,
167
+ 0 ,
142
168
"" ) , null ) ;
143
169
} else {
144
170
callback ( null , "Internal error" ) ;
@@ -188,6 +214,7 @@ function get_collections(user, password, collection, callback) {
188
214
let addressbookcolor_element = response . querySelector ( response_query + " > *|propstat > *|prop > *|addressbook-color" ) ;
189
215
let calendardesc_element = response . querySelector ( response_query + " > *|propstat > *|prop > *|calendar-description" ) ;
190
216
let addressbookdesc_element = response . querySelector ( response_query + " > *|propstat > *|prop > *|addressbook-description" ) ;
217
+ let contentcount_element = response . querySelector ( response_query + " > *|propstat > *|prop > *|getcontentcount" ) ;
191
218
let webcalsource_element = response . querySelector ( response_query + " > *|propstat > *|prop > *|source" ) ;
192
219
let components_query = response_query + " > *|propstat > *|prop > *|supported-calendar-component-set" ;
193
220
let components_element = response . querySelector ( components_query ) ;
@@ -197,11 +224,13 @@ function get_collections(user, password, collection, callback) {
197
224
let color = "" ;
198
225
let description = "" ;
199
226
let source = "" ;
227
+ let count = 0 ;
200
228
if ( resourcetype_element ) {
201
229
if ( resourcetype_element . querySelector ( resourcetype_query + " > *|addressbook" ) ) {
202
230
type = CollectionType . ADDRESSBOOK ;
203
231
color = addressbookcolor_element ? addressbookcolor_element . textContent : "" ;
204
232
description = addressbookdesc_element ? addressbookdesc_element . textContent : "" ;
233
+ count = contentcount_element ? parseInt ( contentcount_element . textContent ) : 0 ;
205
234
} else if ( resourcetype_element . querySelector ( resourcetype_query + " > *|subscribed" ) ) {
206
235
type = CollectionType . WEBCAL ;
207
236
source = webcalsource_element ? webcalsource_element . textContent : "" ;
@@ -221,6 +250,7 @@ function get_collections(user, password, collection, callback) {
221
250
}
222
251
color = calendarcolor_element ? calendarcolor_element . textContent : "" ;
223
252
description = calendardesc_element ? calendardesc_element . textContent : "" ;
253
+ count = contentcount_element ? parseInt ( contentcount_element . textContent ) : 0 ;
224
254
}
225
255
}
226
256
let sane_color = color . trim ( ) ;
@@ -233,7 +263,7 @@ function get_collections(user, password, collection, callback) {
233
263
}
234
264
}
235
265
if ( href . substr ( - 1 ) === "/" && href !== collection . href && type ) {
236
- collections . push ( new Collection ( href , type , displayname , description , sane_color , source ) ) ;
266
+ collections . push ( new Collection ( href , type , displayname , description , sane_color , count , source ) ) ;
237
267
}
238
268
}
239
269
collections . sort ( function ( a , b ) {
@@ -265,6 +295,7 @@ function get_collections(user, password, collection, callback) {
265
295
'<C:supported-calendar-component-set />' +
266
296
'<CR:addressbook-description />' +
267
297
'<CS:source />' +
298
+ '<RADICALE:getcontentcount />' +
268
299
'</prop>' +
269
300
'</propfind>' ) ;
270
301
return request ;
@@ -708,6 +739,7 @@ function CollectionsScene(user, password, collection, onerror) {
708
739
node . classList . remove ( "hidden" ) ;
709
740
let title_form = node . querySelector ( "[data-name=title]" ) ;
710
741
let description_form = node . querySelector ( "[data-name=description]" ) ;
742
+ let contentcount_form = node . querySelector ( "[data-name=contentcount]" ) ;
711
743
let url_form = node . querySelector ( "[data-name=url]" ) ;
712
744
let color_form = node . querySelector ( "[data-name=color]" ) ;
713
745
let delete_btn = node . querySelector ( "[data-name=delete]" ) ;
@@ -739,6 +771,9 @@ function CollectionsScene(user, password, collection, onerror) {
739
771
if ( description_form . textContent . length > 150 ) {
740
772
description_form . classList . add ( "smalltext" ) ;
741
773
}
774
+ if ( collection . type != CollectionType . WEBCAL ) {
775
+ contentcount_form . textContent = ( collection . contentcount > 0 ? collection . contentcount : "No" ) + " item" + ( collection . contentcount == 1 ? "" : "s" ) + " in collection" ;
776
+ }
742
777
let href = SERVER + collection . href ;
743
778
url_form . value = href ;
744
779
download_btn . href = href ;
@@ -939,14 +974,25 @@ function DeleteCollectionScene(user, password, collection) {
939
974
let html_scene = document . getElementById ( "deletecollectionscene" ) ;
940
975
let title_form = html_scene . querySelector ( "[data-name=title]" ) ;
941
976
let error_form = html_scene . querySelector ( "[data-name=error]" ) ;
977
+ let confirmation_txt = html_scene . querySelector ( "[data-name=confirmationtxt]" ) ;
978
+ let delete_confirmation_lbl = html_scene . querySelector ( "[data-name=deleteconfirmationtext]" ) ;
942
979
let delete_btn = html_scene . querySelector ( "[data-name=delete]" ) ;
943
980
let cancel_btn = html_scene . querySelector ( "[data-name=cancel]" ) ;
944
981
982
+ delete_confirmation_lbl . innerHTML = DELETE_CONFIRMATION_TEXT ;
983
+ confirmation_txt . value = "" ;
984
+ confirmation_txt . addEventListener ( "keydown" , onkeydown ) ;
985
+
945
986
/** @type {?number } */ let scene_index = null ;
946
987
/** @type {?XMLHttpRequest } */ let delete_req = null ;
947
988
let error = "" ;
948
989
949
990
function ondelete ( ) {
991
+ let confirmation_text_value = confirmation_txt . value ;
992
+ if ( confirmation_text_value != DELETE_CONFIRMATION_TEXT ) {
993
+ alert ( "Please type the confirmation text to delete this collection." ) ;
994
+ return ;
995
+ }
950
996
try {
951
997
let loading_scene = new LoadingScene ( ) ;
952
998
push_scene ( loading_scene ) ;
@@ -977,6 +1023,13 @@ function DeleteCollectionScene(user, password, collection) {
977
1023
return false ;
978
1024
}
979
1025
1026
+ function onkeydown ( event ) {
1027
+ if ( event . keyCode !== 13 ) {
1028
+ return ;
1029
+ }
1030
+ ondelete ( ) ;
1031
+ }
1032
+
980
1033
this . show = function ( ) {
981
1034
this . release ( ) ;
982
1035
scene_index = scene_stack . length - 1 ;
@@ -1031,6 +1084,8 @@ function CreateEditCollectionScene(user, password, collection) {
1031
1084
let html_scene = document . getElementById ( edit ? "editcollectionscene" : "createcollectionscene" ) ;
1032
1085
let title_form = edit ? html_scene . querySelector ( "[data-name=title]" ) : null ;
1033
1086
let error_form = html_scene . querySelector ( "[data-name=error]" ) ;
1087
+ let href_form = html_scene . querySelector ( "[data-name=href]" ) ;
1088
+ let href_label = html_scene . querySelector ( "label[for=href]" ) ;
1034
1089
let displayname_form = html_scene . querySelector ( "[data-name=displayname]" ) ;
1035
1090
let displayname_label = html_scene . querySelector ( "label[for=displayname]" ) ;
1036
1091
let description_form = html_scene . querySelector ( "[data-name=description]" ) ;
@@ -1057,28 +1112,46 @@ function CreateEditCollectionScene(user, password, collection) {
1057
1112
let type = edit ? collection . type : CollectionType . CALENDAR_JOURNAL_TASKS ;
1058
1113
let color = edit && collection . color ? collection . color : "#" + random_hex ( 6 ) ;
1059
1114
1115
+ if ( ! edit ) {
1116
+ href_form . addEventListener ( "keydown" , cleanHREFinput ) ;
1117
+ }
1118
+
1060
1119
function remove_invalid_types ( ) {
1061
1120
if ( ! edit ) {
1062
1121
return ;
1063
1122
}
1064
1123
/** @type {HTMLOptionsCollection } */ let options = type_form . options ;
1065
1124
// remove all options that are not supersets
1125
+ let valid_type_options = CollectionType . valid_options_for_type ( type ) ;
1066
1126
for ( let i = options . length - 1 ; i >= 0 ; i -- ) {
1067
- if ( ! CollectionType . is_subset ( type , options [ i ] . value ) ) {
1127
+ if ( valid_type_options . indexOf ( options [ i ] . value ) < 0 ) {
1068
1128
options . remove ( i ) ;
1069
1129
}
1070
1130
}
1071
1131
}
1072
1132
1073
1133
function read_form ( ) {
1134
+ if ( ! edit ) {
1135
+ cleanHREFinput ( ) ;
1136
+ let newhreftxtvalue = href_form . value . trim ( ) . toLowerCase ( ) ;
1137
+ if ( ! isValidHREF ( newhreftxtvalue ) ) {
1138
+ alert ( "You must enter a valid HREF" ) ;
1139
+ return false ;
1140
+ }
1141
+ href = collection . href + "/" + newhreftxtvalue + "/" ;
1142
+ }
1074
1143
displayname = displayname_form . value ;
1075
1144
description = description_form . value ;
1076
1145
source = source_form . value ;
1077
1146
type = type_form . value ;
1078
1147
color = color_form . value ;
1148
+ return true ;
1079
1149
}
1080
1150
1081
1151
function fill_form ( ) {
1152
+ if ( ! edit ) {
1153
+ href_form . value = random_uuid ( ) ;
1154
+ }
1082
1155
displayname_form . value = displayname ;
1083
1156
description_form . value = description ;
1084
1157
source_form . value = source ;
@@ -1095,7 +1168,9 @@ function CreateEditCollectionScene(user, password, collection) {
1095
1168
1096
1169
function onsubmit ( ) {
1097
1170
try {
1098
- read_form ( ) ;
1171
+ if ( ! read_form ( ) ) {
1172
+ return false ;
1173
+ }
1099
1174
let sane_color = color . trim ( ) ;
1100
1175
if ( sane_color ) {
1101
1176
let color_match = COLOR_RE . exec ( sane_color ) ;
@@ -1108,7 +1183,7 @@ function CreateEditCollectionScene(user, password, collection) {
1108
1183
}
1109
1184
let loading_scene = new LoadingScene ( ) ;
1110
1185
push_scene ( loading_scene ) ;
1111
- let collection = new Collection ( href , type , displayname , description , sane_color , source ) ;
1186
+ let collection = new Collection ( href , type , displayname , description , sane_color , 0 , source ) ;
1112
1187
let callback = function ( error1 ) {
1113
1188
if ( scene_index === null ) {
1114
1189
return ;
@@ -1141,6 +1216,13 @@ function CreateEditCollectionScene(user, password, collection) {
1141
1216
return false ;
1142
1217
}
1143
1218
1219
+ function cleanHREFinput ( event ) {
1220
+ let currentTxtVal = href_form . value . trim ( ) . toLowerCase ( ) ;
1221
+ //Clean the HREF to remove non lowercase letters and dashes
1222
+ currentTxtVal = currentTxtVal . replace ( / (? ! [ 0 - 9 a - z \- \_ ] ) ./ g, '' ) ;
1223
+ href_form . value = currentTxtVal ;
1224
+ }
1225
+
1144
1226
function onTypeChange ( e ) {
1145
1227
if ( type_form . value == CollectionType . WEBCAL ) {
1146
1228
source_label . classList . remove ( "hidden" ) ;
@@ -1151,6 +1233,17 @@ function CreateEditCollectionScene(user, password, collection) {
1151
1233
}
1152
1234
}
1153
1235
1236
+ function isValidHREF ( href ) {
1237
+ if ( href . length < 1 ) {
1238
+ return false ;
1239
+ }
1240
+ if ( href . indexOf ( "/" ) != - 1 ) {
1241
+ return false ;
1242
+ }
1243
+
1244
+ return true ;
1245
+ }
1246
+
1154
1247
this . show = function ( ) {
1155
1248
this . release ( ) ;
1156
1249
scene_index = scene_stack . length - 1 ;
0 commit comments