Изменения документа AdminUsersSheet

Редактировал(а) Alexey Semenihin 23.10.2026, 15:12

От версии 1.1
отредактировано Admin xWiKi
на 15.10.2019, 13:19
Изменить комментарий: Install extension [org.xwiki.platform:xwiki-platform-administration-ui/11.8.1]
К версии 4.1
отредактировано Alexey Semenihin
на 23.10.2026, 15:12
Изменить комментарий: Install extension [org.xwiki.platform:xwiki-platform-administration-ui/17.10.12]

Сводка

Подробности

Свойства страницы
Автор документа
... ... @@ -1,1 +1,1 @@
1 -XWiki.admin
1 +XWiki.semenihinaa
Синтаксис
... ... @@ -1,1 +1,1 @@
1 -XWiki 2.0
1 +XWiki 2.1
Содержимое
... ... @@ -1,52 +1,93 @@
1 1  {{velocity output="false"}}
2 -#macro (userScopeFilter)
3 - <select name="wiki">
4 - <option selected="selected" value="local">
5 - $escapetool.xml($services.localization.render('rightsmanager.local'))
6 - </option>
7 - <option value="global">
8 - $escapetool.xml($services.localization.render('rightsmanager.global'))
9 - </option>
10 - <option value="both">
11 - $escapetool.xml($services.localization.render('rightsmanager.both'))
12 - </option>
13 - </select>
14 -#end
15 -
16 -#macro (displayUsersLiveTable)
17 - #set ($columnOptions = {
18 - 'name': {'type': 'text', 'sortable': false, 'html': true},
19 - 'first_name': {'type': 'text', 'sortable': false},
20 - 'last_name': {'type': 'text', 'sortable': false},
21 - 'scope': {'type': 'list', 'sortable': false},
22 - '_actions': {
23 - 'actions': [
24 - 'edit',
25 - {'id': 'disable', 'icon': 'lock'},
26 - {'id': 'enable', 'icon': 'unlock'},
27 - 'delete'
28 - ],
29 - 'labels': false
30 - }
2 +#macro (displayUsersLiveData)
3 + #set ($properties = ['name', 'first_name', 'last_name', '_actions'])
4 + #set ($sourceParameters = {
5 + 'template': 'getusers.vm',
6 + 'translationPrefix': 'xe.admin.users.'
31 31   })
32 - #set ($columns = ['name', 'first_name', 'last_name', '_actions'])
33 - #set ($liveTableOptions = {
34 - 'url': $doc.getURL('view', 'xpage=getusers'),
35 - 'translationPrefix': 'xe.admin.users.',
36 - 'outputOnlyHtml': true
37 - })
38 38   #if (!$xcontext.isMainWiki())
39 - #set ($discard = $columns.add(3, 'scope'))
40 - ## We use the top filters option to show only the local users by default because the JavaScript code from the sheet
41 - ## is executed after the livetable is loaded. The JavaScript code removes the top filters and updates the scope filter
42 - ## afterwards.
43 - #set ($liveTableOptions.topFilters = "#userScopeFilter")
9 + #set ($discard = $properties.add(3, 'scope'))
44 44   #end
45 45   <div class="medium-avatars">
46 - #livetable('userstable' $columns $columnOptions $liveTableOptions)
12 + $services.liveData.render({
13 + 'id': 'userstable',
14 + 'source': 'liveTable',
15 + 'properties': $stringtool.join($properties, ','),
16 + 'sourceParameters': $escapetool.url($sourceParameters)
17 + }, {
18 + 'query': {
19 + 'filters': [
20 + {
21 + 'property': 'scope',
22 + 'constraints': [{
23 + 'operator': 'contains',
24 + 'value': 'local'
25 + }]
26 + }
27 + ]
28 + },
29 + 'meta': {
30 + 'propertyDescriptors': [
31 + {
32 + 'id': 'name',
33 + 'displayer': 'html',
34 + 'sortable': false,
35 + 'editable': false
36 + },
37 + {
38 + 'id': 'first_name',
39 + 'sortable': false,
40 + 'editable': false
41 + },
42 + {
43 + 'id': 'last_name',
44 + 'sortable': false,
45 + 'editable': false
46 + },
47 + {
48 + 'id': 'scope',
49 + 'sortable': false,
50 + 'editable': false,
51 + 'filter': {
52 + 'id': 'list',
53 + 'options': [
54 + {'value': 'local', 'label': $services.localization.render('rightsmanager.local')},
55 + {'value': 'global', 'label': $services.localization.render('rightsmanager.global')},
56 + {'value': 'both', 'label': $services.localization.render('rightsmanager.both')}
57 + ]
58 + }
59 + },
60 + {
61 + 'id': '_actions',
62 + 'displayer': {
63 + 'id': 'actions',
64 + 'actions': ['edit', 'disable', 'enable', 'delete']
65 + }
66 + }
67 + ],
68 + 'actions': [
69 + {
70 + 'id': 'disable',
71 + 'icon': 'lock',
72 + 'allowProperty': 'doc.hasdisable',
73 + 'urlProperty': 'doc.disable_url',
74 + 'extraIconClasses': 'text-warning'
75 + },
76 + {
77 + 'id': 'enable',
78 + 'icon': 'unlock',
79 + 'allowProperty': 'doc.hasenable',
80 + 'urlProperty': 'doc.enable_url',
81 + 'extraIconClasses': 'text-success'
82 + }
83 + ]
84 + }
85 + })
47 47   </div>
48 48   <p>
49 - <button type="button" class="btn btn-primary" data-toggle="modal" data-target="${escapetool.h}createUserModal">
88 + <button type="button" class="btn btn-primary" data-toggle="modal" data-target="${escapetool.h}createUserModal"
89 + ## Disable the button until the JavaScript code that handles the user creation is ready.
90 + disabled="disabled">
50 50   $escapetool.xml($services.localization.render('rightsmanager.addnewuser'))
51 51   </button>
52 52   </p>
... ... @@ -70,7 +70,9 @@
70 70   <button type="button" class="btn btn-default" data-dismiss="modal">
71 71   $escapetool.xml($services.localization.render('cancel'))
72 72   </button>
73 - <button type="button" class="btn btn-primary" disabled="disabled">
114 + ## The registration form is not present on page load so we can't bind the submit button without triggerig an
115 + ## HTML validation error. That's why we set the form attribute from JavaScript instead.
116 + <button type="submit" class="btn btn-primary" disabled="disabled">
74 74   $escapetool.xml($services.localization.render('create'))
75 75   </button>
76 76   </div>
... ... @@ -81,7 +81,7 @@
81 81  
82 82  #macro (editUserModal)
83 83   <div class="modal" id="editUserModal" tabindex="-1" role="dialog" aria-labelledby="editUserModal-label"
84 - data-backdrop="static" data-keyboard="false" data-liveTable="#userstable" data-liveTableAction="edit">
127 + data-backdrop="static" data-keyboard="false" data-live-data="#userstable" data-live-data-action="edit">
85 85   <div class="modal-dialog modal-lg" role="document">
86 86   <div class="modal-content">
87 87   <div class="modal-header">
... ... @@ -111,7 +111,7 @@
111 111   ## have script or programming rights.
112 112   #userPicker_import
113 113   <div class="modal" id="deleteUserModal" tabindex="-1" role="dialog" aria-labelledby="deleteUserModal-label"
114 - data-liveTable="#userstable" data-liveTableAction="delete">
157 + data-live-data="#userstable" data-live-data-action="delete">
115 115   <div class="modal-dialog" role="document">
116 116   <div class="modal-content">
117 117   <div class="modal-header">
... ... @@ -165,11 +165,13 @@
165 165  #macro (maybeShowDeleteUserWarning $userReference $right)
166 166   #countPagesLastModifiedBy($userReference)
167 167   #if ($pageCount > 0)
168 - <div class="box errormessage xform">
211 + {{/html}}
212 +
213 + {{error cssClass="xform"}}
214 + {{html}}
169 169   #set ($pageIndexReference = $services.model.createDocumentReference(
170 170   $userReference.wikiReference.name, 'Main', 'AllDocs'))
171 - #set ($pageIndexURL = $xwiki.getURL($pageIndexReference) + '#|t=alldocs&doc.author=' +
172 - $escapetool.url($services.model.serialize($userReference, 'local')))
217 + #set ($pageIndexURL = $xwiki.getURL($pageIndexReference, 'view', "doc.author=${escapetool.url($services.model.serialize($userReference, 'local'))}"))
173 173   #set ($translationKey = "administration.section.users.deleteUser.${right}RightsWarning")
174 174   $services.localization.render($translationKey, ["<a href='$pageIndexURL'>", $pageCount, '</a>'])
175 175   <dl>
... ... @@ -194,7 +194,10 @@
194 194   [$rightTranslation]))</span>
195 195   </dd>
196 196   </dl>
197 - </div>
242 + {{/html}}
243 + {{/error}}
244 +
245 + {{html clean="false"}}
198 198   #end
199 199  #end
200 200  
... ... @@ -247,10 +247,10 @@
247 247   #set ($discard = $xwiki.jsx.use("XWiki.AdminUsersSheet"))
248 248  
249 249   {{html clean="false"}}
250 - #displayUsersLiveTable
251 - #createUserModal
252 - #editUserModal
253 - #deleteUserModal
298 + #displayUsersLiveData()
299 + #createUserModal()
300 + #editUserModal()
301 + #deleteUserModal()
254 254   {{/html}}
255 255   #end
256 256  #end
XWiki.JavaScriptExtension[0]
Код
... ... @@ -1,31 +1,25 @@
1 1  require.config({
2 2   paths: {
3 - 'xwiki-livetable-modal': new XWiki.Document('AdminGroupsSheet', 'XWiki').getURL('jsx', 'r=1')
3 + 'xwiki-livedata-modal': new XWiki.Document('AdminGroupsSheet', 'XWiki').getURL('jsx', 'r=1')
4 4   }
5 5  });
6 6  
7 -//
8 -// Scope Filtering
9 -//
10 -require(['jquery'], function($) {
11 - var options = $('#userstable').prev('.tipfilters').remove().find('select[name="wiki"]').html();
12 - var scopeFilter = $('#userstable .xwiki-livetable-display-filters select[name="scope"]')
13 - scopeFilter.attr('name', 'wiki').html(options);
14 -});
15 -
16 -require(['jquery', 'xwiki-meta', 'xwiki-livetable-modal', 'xwiki-events-bridge'], function($, xm) {
7 +require(['jquery', 'xwiki-meta', 'xwiki-livedata-modal', 'xwiki-events-bridge'], function($, xm) {
17 17   //
18 18   // User Creation
19 19   //
20 - var createUserModal = $('#createUserModal');
11 + const createUserModal = $('#createUserModal');
12 + // We set the form attribute from JavaScript in order to avoid HTML validation errors because the form is not present
13 + // on page load (we load it the first time the modal is shown, see below).
14 + const createUserButton = createUserModal.find('.btn-primary').attr('form', 'register');
21 21  
22 22   var validateCreateUserForm = function(form) {
23 - return form.size() > 0 && (!window.LiveValidation ||
17 + return form.length && (!window.LiveValidation ||
24 24   LiveValidation.massValidate(LiveValidationForm.getInstance(form[0]).fields));
25 25   };
26 26  
27 27   createUserModal.on('show.bs.modal', function() {
28 - var createUserButton = createUserModal.find('.btn-primary').prop('disabled', true);
22 + createUserButton.prop('disabled', true);
29 29   var createUserURL = XWiki.currentDocument.getURL('view', 'xpage=registerinline');
30 30   var createUserModalBody = createUserModal.find('.modal-body').empty().addClass('loading');
31 31   $.get(createUserURL, function(html) {
... ... @@ -35,26 +35,27 @@
35 35   createUserModalBody.removeClass('loading').append(createUserForm);
36 36   $(document).trigger('xwiki:dom:updated', {'elements': createUserModalBody.toArray()});
37 37   createUserForm.find(':input').filter(':visible').first().focus();
38 - createUserButton.prop('disabled', createUserForm.size() === 0);
32 + createUserButton.prop('disabled', !createUserForm.length);
39 39   });
40 - }).on('click', '.btn-primary', function(event) {
34 + }).on('submit', 'form#register', function(event) {
35 + event.preventDefault();
41 41   var createUserForm = createUserModal.find('form#register');
42 42   if (!validateCreateUserForm(createUserForm)) {
43 43   return;
44 44   }
45 - var createUserButton = $(this).prop('disabled', true);
40 + createUserButton.prop('disabled', true);
46 46   var notification = new XWiki.widgets.Notification(
47 47   $jsontool.serialize($services.localization.render('xe.admin.users.create.inProgress')),
48 48   'inprogress'
49 49   );
50 - $.post(createUserForm.attr('action'), createUserForm.serialize()).done(function(html) {
45 + $.post(createUserForm.attr('action'), createUserForm.serialize()).then(html => {
51 51   var errorMessage = $('<div/>').html(html).find('.errormessage, .LV_validation_message.LV_invalid');
52 - if (errorMessage.size() > 0) {
47 + if (errorMessage.length) {
53 53   createUserButton.prop('disabled', false);
54 54   notification.replace(new XWiki.widgets.Notification(errorMessage.text(), 'error'));
55 55   } else {
56 56   createUserModal.modal('hide');
57 - window.livetable_userstable.refresh();
52 + $('#userstable').data('liveData').updateEntries();
58 58   $('#userstable').trigger('xwiki:user:created');
59 59   notification.replace(new XWiki.widgets.Notification(
60 60   $jsontool.serialize($services.localization.render('xe.admin.users.create.done')),
... ... @@ -61,7 +61,7 @@
61 61   'done'
62 62   ));
63 63   }
64 - }).fail(function (response) {
59 + }).catch(() => {
65 65   createUserButton.prop('disabled', false);
66 66   notification.replace(new XWiki.widgets.Notification(
67 67   $jsontool.serialize($services.localization.render('xe.admin.users.create.failed')),
... ... @@ -78,20 +78,22 @@
78 78   var loadEditForm = function(forceLock) {
79 79   var saveButton = editUserModal.find('.btn-primary').prop('disabled', true);
80 80   var rowData = editUserModal.data('rowData');
81 - var userReference = XWiki.Model.resolve(rowData.doc_fullName, XWiki.EntityType.DOCUMENT, [rowData.doc_wiki]);
76 + var userReference = XWiki.Model.resolve(rowData['doc.fullName'], XWiki.EntityType.DOCUMENT, [rowData['doc.wiki']]);
82 82   var userDocument = new XWiki.Document(userReference);
83 83   var editUserURL = userDocument.getURL('edit');
84 84   var parameters = {xpage: 'edituser'};
85 85   if (forceLock === true) {
81 + parameters.force_token = xm.form_token;
86 86   parameters.force = 1;
87 87   }
88 88   editUserModal.find('.modal-body').first().empty().addClass('loading').load(editUserURL, parameters, function() {
89 - this.removeClass('loading');
90 - this.find('.column.half').removeClass('half').addClass('full');
91 - $(document).trigger('xwiki:dom:updated', {'elements': this.toArray()});
85 + var self = $(this);
86 + self.removeClass('loading');
87 + self.find('.column.half').removeClass('half').addClass('full');
88 + $(document).trigger('xwiki:dom:updated', {'elements': self.toArray()});
92 92   var editUserForm = editUserModal.find('form#edituser');
93 93   editUserForm.find(':input').filter(':visible').first().focus();
94 - saveButton.prop('disabled', editUserForm.size() === 0);
91 + saveButton.prop('disabled', !editUserForm.length);
95 95   });
96 96   };
97 97  
... ... @@ -109,14 +109,14 @@
109 109   'inprogress'
110 110   );
111 111   $(document).trigger('xwiki:actions:beforeSave');
112 - $.post(editForm.attr('action'), editForm.serialize()).done(function() {
109 + $.post(editForm.attr('action'), editForm.serialize()).then(() => {
113 113   $(document).trigger('xwiki:document:saved');
114 - editUserModal.modal('hide').data('liveTable').refresh();
111 + editUserModal.modal('hide').data('liveDataElement').data('liveData').updateEntries();
115 115   notification.replace(new XWiki.widgets.Notification(
116 116   $jsontool.serialize($services.localization.render('core.editors.saveandcontinue.notification.done')),
117 117   'done'
118 118   ));
119 - }).fail(function (response) {
116 + }).catch(response => {
120 120   saveButton.prop('disabled', false);
121 121   var message = $jsontool.serialize($services.localization.render('core.editors.saveandcontinue.notification.error',
122 122   ['__reason__']));
... ... @@ -145,29 +145,32 @@
145 145   failed: $jsontool.serialize($services.localization.render('administration.section.users.disableUser.failed'))
146 146   }
147 147   }
148 - var onToggleUser = function(action, event) {
149 - event.preventDefault();
150 - var actionTrigger = $(this);
151 - if (actionTrigger.hasClass('pending')) {
152 - // Another request is already in progress.
153 - return;
154 - }
155 - actionTrigger.addClass('pending');
156 - var notification = new XWiki.widgets.Notification(notificationMessage[action].inProgress, 'inprogress');
157 - // The enable and disable actions redirect to the user profile by default which may take a lot of time to render so
158 - // we redirect to a URL that doesn't render anything.
159 - var emptyResponseURL = new XWiki.Document('AdminUsersSheet', 'XWiki').getURL('get', 'outputSyntax=plain');
160 - $.post(actionTrigger.attr('href'), {'xredirect': emptyResponseURL}).done(function() {
161 - window['livetable_userstable'].refresh();
162 - notification.replace(new XWiki.widgets.Notification(notificationMessage[action].done, 'done'));
163 - }).fail(function() {
164 - notification.replace(new XWiki.widgets.Notification(notificationMessage[action].failed, 'error'));
165 - }).always(function() {
166 - actionTrigger.removeClass('pending');
167 - });
168 - };
169 - $('#userstable').on('click', 'a.actionenable', $.proxy(onToggleUser, null, 'enable'));
170 - $('#userstable').on('click', 'a.actiondisable', $.proxy(onToggleUser, null, 'disable'));
145 +
146 + function onToggleUser(action) {
147 + return function (event) {
148 + event.preventDefault();
149 + var actionTrigger = $(this);
150 + if (actionTrigger.hasClass('pending')) {
151 + // Another request is already in progress.
152 + return;
153 + }
154 + actionTrigger.addClass('pending');
155 + var notification = new XWiki.widgets.Notification(notificationMessage[action].inProgress, 'inprogress');
156 + // The enable and disable actions redirect to the user profile by default which may take a lot of time to
157 + // render so we redirect to a URL that doesn't render anything.
158 + var emptyResponseURL = new XWiki.Document('AdminUsersSheet', 'XWiki').getURL('get', 'outputSyntax=plain');
159 + Promise.resolve($.post(actionTrigger.attr('href'), {'xredirect': emptyResponseURL})).then(function () {
160 + $('#userstable').data('liveData').updateEntries();
161 + notification.replace(new XWiki.widgets.Notification(notificationMessage[action].done, 'done'));
162 + }).catch(function () {
163 + notification.replace(new XWiki.widgets.Notification(notificationMessage[action].failed, 'error'));
164 + }).finally(function () {
165 + actionTrigger.removeClass('pending');
166 + });
167 + };
168 + }
169 + $(document).on('click', '#userstable a.action_enable', onToggleUser('enable'));
170 + $(document).on('click', '#userstable a.action_disable', onToggleUser('disable'));
171 171   })();
172 172  
173 173   //
... ... @@ -201,9 +201,9 @@
201 201  
202 202   deleteUserModal.on('show.bs.modal', function() {
203 203   var rowData = deleteUserModal.data('rowData');
204 - var userReference = rowData.doc_fullName;
205 - if (XWiki.currentWiki !== rowData.doc_wiki) {
206 - userReference = XWiki.Model.resolve(userReference, XWiki.EntityType.DOCUMENT, [rowData.doc_wiki]);
204 + var userReference = rowData['doc.fullName'];
205 + if (XWiki.currentWiki !== rowData['doc.wiki']) {
206 + userReference = XWiki.Model.resolve(userReference, XWiki.EntityType.DOCUMENT, [rowData['doc.wiki']]);
207 207   userReference = XWiki.Model.serialize(userReference);
208 208   }
209 209   deleteUserModal.data('userReference', userReference);
... ... @@ -224,9 +224,9 @@
224 224   newAuthor: newAuthor,
225 225   requiredRight: requiredRight
226 226   });
227 - return newAuthorValidationRequest;
227 + return Promise.resolve(newAuthorValidationRequest);
228 228   } else {
229 - return $.Deferred().resolve({valid: true}).promise();
229 + return Promise.resolve({valid: true});
230 230   }
231 231   };
232 232   deleteUserModal.on('change', '#newAuthor', function(event) {
... ... @@ -235,17 +235,17 @@
235 235   var newAuthorField = $(event.target);
236 236   // Hide the previous error message.
237 237   newAuthorField.nextAll('.xErrorMsg').addClass('hidden');
238 - validateNewAuthor(newAuthorField.val(), newAuthorField.data('requiredRight')).done(function(result) {
238 + validateNewAuthor(newAuthorField.val(), newAuthorField.data('requiredRight')).then(function(result) {
239 239   if (result.valid === false) {
240 240   newAuthorField.nextAll('.xErrorMsg').removeClass('hidden');
241 241   }
242 - }).always(function() {
242 + }).finally(function() {
243 243   // Re-enable the modal submit button.
244 244   deleteUserButton.prop('disabled', false);
245 245   });
246 246   });
247 247  
248 - deleteUserButton.click(function() {
248 + deleteUserButton.on('click', function() {
249 249   var notification = new XWiki.widgets.Notification(
250 250   $jsontool.serialize($services.localization.render('xe.admin.users.delete.inProgress')),
251 251   'inprogress'
... ... @@ -256,14 +256,14 @@
256 256   docname: userReference,
257 257   newAuthor: deleteUserModal.find('#newAuthor').val(),
258 258   form_token: xm.form_token
259 - }).done(function() {
260 - deleteUserModal.data('liveTable').deleteRow(deleteUserModal.data('rowIndex'));
261 - deleteUserModal.data('liveTableElement').trigger('xwiki:user:deleted', {reference: userReference});
259 + }).then(() => {
260 + deleteUserModal.data('liveDataElement').data('liveData').updateEntries();
261 + deleteUserModal.data('liveDataElement').trigger('xwiki:user:deleted', {reference: userReference});
262 262   notification.replace(new XWiki.widgets.Notification(
263 263   $jsontool.serialize($services.localization.render('xe.admin.users.delete.done')),
264 264   'done'
265 265   ));
266 - }).fail(function() {
266 + }).catch(() => {
267 267   notification.replace(new XWiki.widgets.Notification(
268 268   $jsontool.serialize($services.localization.render('xe.admin.users.delete.failed')),
269 269   'error'
... ... @@ -270,4 +270,7 @@
270 270   ));
271 271   });
272 272   });
273 +
274 + // Finally, enable the user creation.
275 + $('button[data-target="#createUserModal"]').prop('disabled', false);
273 273  });
XWiki.StyleSheetExtension[0]
Код
... ... @@ -1,19 +3,4 @@
1 -#template('colorThemeInit.vm')
2 -
3 3  /**
4 - * Users Live Table
5 - */
6 -#userstable td[data-title] {
7 - vertical-align: middle;
8 -}
9 -#userstable .actiondisable .action-icon {
10 - color: $theme.notificationWarningColor;
11 -}
12 -#userstable .actionenable .action-icon {
13 - color: $theme.notificationSuccessColor;
14 -}
15 -
16 -/**
17 17   * Delete User Modal
18 18   */
19 19  #deleteUserModal .userName {
Content Type
... ... @@ -1,1 +1,1 @@
1 -CSS
1 +LESS
Разбор содержимого
... ... @@ -1,1 +1,1 @@
1 -Да
1 +Нет