Changes for page LiveTable View Sheet
Last modified by Mark Kohlmann on 2025/01/19 06:47
From version 4.1
edited by Mark Kohlmann
on 2025/01/19 06:47
on 2025/01/19 06:47
Change comment:
Install extension [org.xwiki.platform:xwiki-platform-appwithinminutes-ui/16.10.2]
To version 2.1
edited by Mark Kohlmann
on 2020/04/03 03:53
on 2020/04/03 03:53
Change comment:
Install extension [org.xwiki.platform:xwiki-platform-appwithinminutes-ui/12.2]
Summary
-
Page properties (2 modified, 0 added, 0 removed)
-
Attachments (0 modified, 1 added, 0 removed)
-
Objects (2 modified, 0 added, 0 removed)
Details
- Page properties
-
- Author
-
... ... @@ -1,1 +1,1 @@ 1 -XWiki. mark\.kohlmann@chiefintegrations\.com1 +XWiki.MarkKohlmann - Content
-
... ... @@ -11,8 +11,16 @@ 11 11 ## Display the live table only if it was generated. 12 12 #if ($doc.content.length() > 0) 13 13 = $services.localization.render('platform.appwithinminutes.appLiveTableHeading') = 14 - 15 - {{include reference="" author="target"/}} 14 + ## We don't use the Include macro (with empty reference) because we want the content to be executed with the rights 15 + ## of the current document rather than the rights of the sheet. This is important because the user can modify the 16 + ## content of the application home page which means we could execute untrusted content with the rights of the sheet. 17 + ## Ideally we should use the Display macro with a parameter to disable the sheet, but we don't have this parameter. 18 + ## We don't clean the HTML content because getRenderedContent() should produce clean HTML, unless the user has 19 + ## disabled the HTML cleaning, in which case he will get what he asked for. Note that one good reason to disable 20 + ## HTML cleaning is to preserve the whitespaces in the attribute values. 21 + ## Escape {{ in the rendered content to be sure that the HTML macro is not closed unintentionally. 22 + {{html clean="false"}}$doc.getRenderedContent($doc.content, 23 + $doc.syntax.toIdString()).replace('{{', '&#123;&#123;'){{/html}} 16 16 #end 17 17 #end 18 18 ... ... @@ -51,7 +51,7 @@ 51 51 ((( 52 52 = $services.localization.render('platform.appwithinminutes.appHomePageActionsHeading') = 53 53 #if ($hasCreateData) 54 - * [[ {{displayIcon name="add"/}}$services.localization.render('platform.appwithinminutes.appHomePageAddEntryHint')>>||anchor="AddNewEntry" class="action add"]]##62 + * [[$services.localization.render('platform.appwithinminutes.appHomePageAddEntryHint')>>||anchor="AddNewEntry" class="action add"]]## 55 55 #if ("$!templateProvider.getValue('terminal')" == '1') 56 56 #set ($entryReference = $services.model.createDocumentReference('__entryName__', $dataSpaceRef)) 57 57 #else ... ... @@ -60,7 +60,6 @@ 60 60 #end 61 61 ## We need to set the title if we want to be able to sort or filter the doc.title live table column. 62 62 #set ($params = { 63 - 'form_token': $services.csrf.token, 64 64 'template': "${className}Template", 65 65 'title': '__entryName__', 66 66 'parent': $services.model.serialize($doc.documentReference, 'local') ... ... @@ -84,10 +84,10 @@ 84 84 'appName': $doc.space, 85 85 'resolve': true 86 86 })) 87 - * [[ {{displayIcon name="edit"/}}$services.localization.render('platform.appwithinminutes.appHomePageEditAppLabel')>>AppWithinMinutes.CreateApplication||queryString="$queryString" class="action edit"]]94 + * [[$services.localization.render('platform.appwithinminutes.appHomePageEditAppLabel')>>AppWithinMinutes.CreateApplication||queryString="$queryString" class="action edit"]] 88 88 #end 89 89 #if ($hasEditTranslations) 90 - * [[ {{displayIcon name="translate"/}}$services.localization.render('platform.appwithinminutes.appHomePageTranslateAppLabel')>>path:${xwiki.getURL($translationsRef, 'edit', 'editor=wiki')}||class="action translate"]]97 + * [[$services.localization.render('platform.appwithinminutes.appHomePageTranslateAppLabel')>>path:${xwiki.getURL($translationsRef, 'edit', 'editor=wiki')}||class="action translate"]] 91 91 #end 92 92 #if ($hasDeleteData) 93 93 #set ($deleteDataURL = $xwiki.getURL('AppWithinMinutes.DeleteApplication', 'view', $escapetool.url({ ... ... @@ -96,7 +96,7 @@ 96 96 'scope': 'entries', 97 97 'xredirect': $doc.getURL() 98 98 }))) 99 - * [[ {{displayIcon name="cross"/}}$services.localization.render('platform.appwithinminutes.appHomePageDeleteEntriesLabel')>>path:${deleteDataURL}||class="action deleteData"]]106 + * [[$services.localization.render('platform.appwithinminutes.appHomePageDeleteEntriesLabel')>>path:${deleteDataURL}||class="action deleteData"]] 100 100 #end 101 101 #if ($hasDeleteApplication) 102 102 #set ($deleteAppURL = $xwiki.getURL('AppWithinMinutes.DeleteApplication', 'view', $escapetool.url({ ... ... @@ -104,7 +104,7 @@ 104 104 'resolve': true, 105 105 'xredirect': $doc.getURL() 106 106 }))) 107 - * [[ {{displayIcon name="trash"/}}$services.localization.render('platform.appwithinminutes.appHomePageDeleteAppLabel')>>path:${deleteAppURL}||class="action delete"]]114 + * [[$services.localization.render('platform.appwithinminutes.appHomePageDeleteAppLabel')>>path:${deleteAppURL}||class="action delete"]] 108 108 #end 109 109 ))) 110 110 #end ... ... @@ -115,30 +115,23 @@ 115 115 data-backdrop="static" data-keyboard="false"> 116 116 <div class="modal-dialog" role="document"> 117 117 <form class="modal-content xform"> 118 - ## The fieldset allows us to disable and enable the entire form quickly and easy. 119 - <fieldset> 120 - <div class="modal-header"> 121 - <button type="button" class="close" data-dismiss="modal" 122 - title="$escapetool.xml($services.localization.render('appWithinMinutes.renameApp.close'))" 123 - aria-label="$escapetool.xml($services.localization.render('appWithinMinutes.renameApp.close'))"> 124 - <span aria-hidden="true">×</span> 125 - </button> 126 - <span class="modal-title" id="renameAppModal-label"> 127 - $escapetool.xml($services.localization.render('appWithinMinutes.renameApp.label')) 128 - </span> 129 - </div> 130 - <div class="modal-body"> 131 - #renameAppModalBody 132 - </div> 133 - <div class="modal-footer"> 134 - <button type="button" class="btn btn-default" data-dismiss="modal"> 135 - $escapetool.xml($services.localization.render('cancel')) 136 - </button> 137 - <button type="submit" class="btn btn-primary" disabled="disabled"> 138 - $escapetool.xml($services.localization.render('core.rename.submit')) 139 - </button> 140 - </div> 141 - </fieldset> 125 + <div class="modal-header"> 126 + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> 127 + <span aria-hidden="true">×</span> 128 + </button> 129 + <span class="modal-title" id="renameAppModal-label">Rename Application</span> 130 + </div> 131 + <div class="modal-body"> 132 + #renameAppModalBody 133 + </div> 134 + <div class="modal-footer"> 135 + <button type="button" class="btn btn-default" data-dismiss="modal"> 136 + $escapetool.xml($services.localization.render('cancel')) 137 + </button> 138 + <button type="submit" class="btn btn-primary" disabled="disabled"> 139 + $escapetool.xml($services.localization.render('core.rename.submit')) 140 + </button> 141 + </div> 142 142 </form> 143 143 </div> 144 144 </div> ... ... @@ -204,7 +204,7 @@ 204 204 #set ($classFullName = $doc.getValue('class')) 205 205 #if ("$!classFullName" == '' || !$xwiki.exists($classFullName)) 206 206 {{warning}} 207 - {{translation key="platform.appwithinminutes.appHomePageMovedWarning"/}} 207 + $services.icon.render('warning') {{translation key="platform.appwithinminutes.appHomePageMovedWarning"/}} 208 208 {{/warning}} 209 209 210 210 #end
- locate.png
-
- Author
-
... ... @@ -1,0 +1,1 @@ 1 +XWiki.MarkKohlmann - Size
-
... ... @@ -1,0 +1,1 @@ 1 +746 bytes - Content
- XWiki.JavaScriptExtension[0]
-
- Code
-
... ... @@ -58,9 +58,8 @@ 58 58 /** 59 59 * Rename Application 60 60 */ 61 -require(['jquery', 'bootstrap', 'xwiki-form-validation-async'], function($) { 62 - ## Note: if not currentApp request param is not passed, we default to the wiki home page reference to avoid a NPE 63 - #set ($currentDocReference = $xwiki.getDocument("$!request.currentApp").getDocumentReference()) 61 +require(['jquery', 'bootstrap'], function($) { 62 + #set ($currentDocReference = $xwiki.getDocument($request.currentApp).getDocumentReference()) 64 64 // if we cannot find any extension related to this page app, it's not part of an extension. 65 65 var isNotAnExtension = $services.extension.xar.getInstalledExtensions($currentDocReference).isEmpty(); 66 66 ... ... @@ -70,7 +70,7 @@ 70 70 } 71 71 // Hijack the rename page action. 72 72 var renameAppModal = $('#renameAppModal'); 73 - $('#tmActionRename'). on('click',function(event) {72 + $('#tmActionRename').click(function(event) { 74 74 event.preventDefault(); 75 75 renameAppModal.modal(); 76 76 }); ... ... @@ -78,6 +78,7 @@ 78 78 // Form validation. 79 79 var appNameInput = $('#renameAppTitle'); 80 80 var appParentInput = $('#renameAppParentReference'); 80 + var submitButton = renameAppModal.find('.btn-primary[type="submit"]'); 81 81 82 82 var appNameEmptyError = renameAppModal.find('.appNameEmptyError'); 83 83 var pageExistsError = renameAppModal.find('.pageExistsError'); ... ... @@ -100,44 +100,52 @@ 100 100 101 101 var startValidation = function() { 102 102 if (appNameInput.val() === '') { 103 - returnPromise.reject(appNameEmptyError);103 + endValidation(appNameEmptyError); 104 104 } else { 105 105 var newAppHomePage = getNewAppHomePage(); 106 106 if (newAppHomePage.documentReference.equals(XWiki.currentDocument.documentReference)) { 107 - returnPromise.reject(pageExistsError);107 + endValidation(pageExistsError); 108 108 } else { 109 - return new Promise((resolve, reject) => { 110 - $.ajax({ 111 - type: 'HEAD', 112 - url: newAppHomePage.getURL() 113 - }).then(reject.bind(null, pageExistsError), response => { 114 - if (response.status === 404) { 115 - $.ajax({ 116 - type: 'HEAD', 117 - url: newAppHomePage.getURL('edit') 118 - }).then( 119 - () => resolve(), 120 - () => reject(locationForbiddenError) 121 - ); 122 - } else if (response.status === 403) { 123 - reject(locationForbiddenError); 124 - } else { 125 - resolve(); 126 - } 127 - }); 109 + $.ajax({ 110 + type: 'HEAD', 111 + url: newAppHomePage.getURL() 112 + }).done(function() { 113 + endValidation(pageExistsError); 114 + }).fail(function(response) { 115 + if (response.status === 404) { 116 + $.ajax({ 117 + type: 'HEAD', 118 + url: newAppHomePage.getURL('edit') 119 + }).done(function() { 120 + endValidation(); 121 + }).fail(function() { 122 + endValidation(locationForbiddenError); 123 + }); 124 + } else if (response.status === 403) { 125 + endValidation(locationForbiddenError); 126 + } else { 127 + endValidation(); 128 + } 128 128 }); 129 129 } 130 130 } 131 131 }; 132 132 134 + var endValidation = function(error) { 135 + if (error) { 136 + error.show(); 137 + } 138 + appNameInput.removeClass('loading'); 139 + submitButton.prop('disabled', !!error); 140 + }; 141 + 142 + var validationTimeout; 133 133 var scheduleValidation = function() { 134 - // Hide allerror messages before starting theasynchronousvalidation.144 + clearTimeout(validationTimeout); 135 135 renameAppModal.find('.xErrorMsg').hide(); 136 - appNameInput.addClass('loading').validateAsync(startValidation, 500, 'awm').catch((error) => { 137 - error.show(); 138 - }).finally(() => { 139 - appNameInput.removeClass('loading'); 140 - }); 146 + appNameInput.addClass('loading'); 147 + submitButton.prop('disabled', true); 148 + validationTimeout = setTimeout(startValidation, 500); 141 141 }; 142 142 143 143 appNameInput.add(appParentInput).on('input', scheduleValidation); ... ... @@ -159,7 +159,7 @@ 159 159 renameAppModal.modal('show'); 160 160 }); 161 161 162 - renameAppModal.find('form'). on('submit',function(event) {170 + renameAppModal.find('form').submit(function(event) { 163 163 event.preventDefault(); 164 164 renameApp(getRenameData($(this))); 165 165 }); ... ... @@ -186,13 +186,13 @@ 186 186 187 187 var renameApp = function(data) { 188 188 // Disable the form to prevent it from being submitted twice. 189 - renameAppModal.find(' fieldset').prop('disabled', true);197 + renameAppModal.find(':input').prop('disabled', true); 190 190 var notification = new XWiki.widgets.Notification( 191 191 $jsontool.serialize($services.localization.render('appWithinMinutes.renameApp.inProgress')), 192 192 'inprogress' 193 193 ); 194 194 var renameAppURL = new XWiki.Document('RenameApplication', 'AppWithinMinutes').getURL('get'); 195 - Promise.resolve($.post(renameAppURL, data)).then(updateAppHomePage).then(function() {203 + $.post(renameAppURL, data).then(updateAppHomePage).done(function() { 196 196 renameAppModal.modal('hide'); 197 197 notification.replace(new XWiki.widgets.Notification( 198 198 $jsontool.serialize($services.localization.render('appWithinMinutes.renameApp.done')), ... ... @@ -202,29 +202,33 @@ 202 202 setTimeout(function() { 203 203 window.location.href = getNewAppHomePage().getURL(); 204 204 }, 0); 205 - }). catch(function() {213 + }).fail(function() { 206 206 notification.replace(new XWiki.widgets.Notification( 207 207 $jsontool.serialize($services.localization.render('appWithinMinutes.renameApp.failed')), 208 208 'error' 209 209 )); 210 - }). finally(function() {218 + }).always(function() { 211 211 // Re-enable the form. 212 - renameAppModal.find(' fieldset').prop('disabled', false);220 + renameAppModal.find(':input').prop('disabled', false); 213 213 }); 214 214 }; 215 215 216 216 var updateAppHomePage = function() { 217 - return new Promise((resolve, reject) => { 218 - var newAppHomePageEditURL = getNewAppHomePage().getURL('edit'); 219 - $('<div/>').load(newAppHomePageEditURL + ' #inline', function() { 220 - var formData = $(this).children('form#inline').serializeArray(); 221 - if (formData.length) { 222 - formData.push({name: 'xaction_save', value: true}); 223 - $.post(newAppHomePageEditURL, formData).then(resolve, reject); 224 - } else { 225 - reject(); 226 - } 227 - }); 225 + var deferred = $.Deferred(); 226 + var newAppHomePageEditURL = getNewAppHomePage().getURL('edit'); 227 + $('<div/>').load(newAppHomePageEditURL + ' #inline', function() { 228 + var formData = $(this).children('form#inline').serializeArray(); 229 + if (formData.length > 0) { 230 + formData.push({name: 'xaction_save', value: true}); 231 + $.post(newAppHomePageEditURL, formData).done(function() { 232 + deferred.resolve(); 233 + }).fail(function() { 234 + deferred.reject(); 235 + }); 236 + } else { 237 + deferred.reject(); 238 + } 228 228 }); 240 + return deferred.promise(); 229 229 } 230 230 });
- XWiki.StyleSheetExtension[0]
-
- Code
-
... ... @@ -16,10 +16,30 @@ 16 16 background: none no-repeat scroll 0 center transparent; 17 17 display: block; 18 18 font-size: .8em; 19 - padding: .3em .3em .3em .3em;19 + padding: .3em .3em .3em 20px; 20 20 text-transform: uppercase; 21 21 } 22 22 23 +#actionBox .action.edit { 24 + background-image: url("$xwiki.getSkinFile('icons/silk/application_edit.png')"); 25 +} 26 + 27 +#actionBox .action.translate { 28 + background-image: url("$doc.getAttachmentURL('locate.png')"); 29 +} 30 + 31 +#actionBox .action.delete { 32 + background-image: url("$xwiki.getSkinFile('icons/silk/application_delete.png')"); 33 +} 34 + 35 +#actionBox .action.deleteData { 36 + background-image: url("$xwiki.getSkinFile('icons/silk/application_form_delete.png')"); 37 +} 38 + 39 +#actionBox .action.add { 40 + background-image: url("$xwiki.getSkinFile('icons/silk/add.png')"); 41 +} 42 + 23 23 #entryNamePopup { 24 24 margin-right: 20px; 25 25 }