Changes for page InplaceEditing
From version 4.1
edited by Nazzareno Pompei
on 28/10/2022 08:43
on 28/10/2022 08:43
Change comment:
Install extension [org.xwiki.platform:xwiki-platform-edit-ui/14.9]
Summary
-
Page properties (2 modified, 0 added, 0 removed)
-
Objects (2 modified, 1 added, 0 removed)
Details
- Page properties
-
- Author
-
... ... @@ -1,1 +1,1 @@ 1 -XWiki.N azzarenoPompei1 +XWiki.NPompei - Content
-
... ... @@ -9,15 +9,11 @@ 9 9 #jsonResponse($editConfirmation) 10 10 #else 11 11 ## Lock the document for editing. 12 - #set ($ lockParams= {12 + #set ($discard = $response.sendRedirect($tdoc.getURL('lock', $escapetool.url({ 13 13 'ajax': 1, 14 14 'action': $request.lockAction, 15 15 'language': $tdoc.realLocale 16 - }) 17 - #if ($request.force == 'true') 18 - #set ($lockParams.force = 1) 19 - #end 20 - #set ($discard = $response.sendRedirect($tdoc.getURL('lock', $escapetool.url($lockParams)))) 16 + })))) 21 21 #end 22 22 #end 23 23 {{/velocity}}
- XWiki.JavaScriptExtension[0]
-
- Code
-
... ... @@ -1,11 +7,10 @@ 1 -(function(config) { 2 - "use strict"; 3 - 4 -const paths = config.paths; 5 -const l10n = config.l10n; 6 - 7 7 require.config({ 8 - paths: paths.js 2 + paths: { 3 + 'actionButtons': $jsontool.serialize($xwiki.getSkinFile('js/xwiki/actionbuttons/actionButtons.js', true)), 4 + 'autoSave': $jsontool.serialize($xwiki.getSkinFile('js/xwiki/editors/autosave.js', true)), 5 + // Required in case the user needs to resolve merge conflicts on save. 6 + 'xwiki-diff': $jsontool.serialize($xwiki.getSkinFile('uicomponents/viewers/diff.js')) 7 + } 9 9 }); 10 10 11 11 define('xwiki-document-api', ['jquery'], function($) { ... ... @@ -16,36 +16,23 @@ 16 16 17 17 return { 18 18 /** 19 - * @return this document's plain title 20 - */ 21 - getPlainTitle() { 22 - return $('<div/>').html(this.renderedTitle || this.title).text(); 23 - }, 24 - 25 - /** 26 26 * @return this document's real locale 27 27 */ 28 28 getRealLocale: function() { 29 29 var realLocale = this.language; 30 - if (typeof realLocale !== 'string' || realLocale === '') { 31 - realLocale = this.getDefaultLocale(); 22 + if (typeof realLocale === 'string' && realLocale !== '') { 23 + // This document is a translation. 24 + } else if (this.translations && typeof this.translations['default'] === 'string') { 25 + // This is the original document. 26 + realLocale = this.translations['default']; 27 + } else { 28 + // The document locale is not specified. Use the UI locale. 29 + realLocale = $('html').attr('lang'); 32 32 } 33 33 return realLocale; 34 34 }, 35 35 36 36 /** 37 - * @return this document's default locale 38 - */ 39 - getDefaultLocale: function() { 40 - if (this.translations && typeof this.translations['default'] === 'string') { 41 - return this.translations['default']; 42 - } else { 43 - // The default locale is not specified. Use the UI locale. 44 - return $('html').attr('lang'); 45 - } 46 - }, 47 - 48 - /** 49 49 * @return the URL that can be used to perform the specified action on this document 50 50 */ 51 51 getURL: function(action, queryString, fragment) { ... ... @@ -82,29 +82,27 @@ 82 82 var queryString = { 83 83 xpage: 'get', 84 84 outputTitle: true, 71 + outputSyntax: forView ? null : 'annotatedxhtml', 85 85 language: this.getRealLocale(), 86 86 // Make sure the response is not retrieved from cache (IE11 doesn't obey the caching HTTP headers). 87 87 timestamp: new Date().getTime() 88 88 }; 89 - if (!forView) { 90 - // We need the annotated HTML when editing in order to be able to protect the rendering transformations and to 91 - // be able to recreate the wiki syntax. 92 - queryString.outputSyntax = 'annotatedhtml'; 93 - queryString.outputSyntaxVersion = '5.0' 94 - // Currently, only the macro transformations are protected and thus can be edited. 95 - // See XRENDERING-78: Add markers to modified XDOM by Transformations/Macros 96 - queryString.transformations = 'macro'; 97 - } 98 - return Promise.resolve($.get(this.getURL('view'), queryString)).then(html => { 76 + var thisXWikiDocument = this; 77 + return $.get(this.getURL('view'), queryString).fail(function() { 78 + new XWiki.widgets.Notification( 79 + $jsontool.serialize($services.localization.render('edit.inplace.page.renderFailed')), 80 + 'error' 81 + ); 82 + }).then(function(html) { 99 99 // Render succeeded. 100 100 var container = $('<div/>').html(html); 101 - return $.extend(this, { 85 + return $.extend(thisXWikiDocument, { 102 102 renderedTitle: container.find('#document-title h1').html(), 103 103 renderedContent: container.find('#xwikicontent').html() 104 104 }); 105 - } ).catch(()=>{106 - newXWiki.widgets.Notification(l10n['edit.inplace.page.renderFailed'], 'error');107 - return Promise.reject(this);89 + }, function() { 90 + // Render failed. 91 + return thisXWikiDocument; 108 108 }); 109 109 }, 110 110 ... ... @@ -114,19 +114,20 @@ 114 114 * @return a promise that resolves to this document instance if the reload request succeeds 115 115 */ 116 116 reload: function() { 117 - return Promise.resolve($.getJSON(this.getRestURL(), { 101 + var thisXWikiDocument = this; 102 + return $.getJSON(this.getRestURL(), { 118 118 // Make sure the response is not retrieved from cache (IE11 doesn't obey the caching HTTP headers). 119 119 timestamp: new Date().getTime() 120 - }) ).then(newXWikiDocument=>{105 + }).then(function(newXWikiDocument) { 121 121 // Reload succeeded. 122 122 // Resolve the document reference. 123 - this.documentReference = XWiki.Model.resolve(newXWikiDocument.id, XWiki.EntityType.DOCUMENT); 108 + thisXWikiDocument.documentReference = XWiki.Model.resolve(newXWikiDocument.id, XWiki.EntityType.DOCUMENT); 124 124 // We were able to load the document so it's not new. 125 - this.isNew = false; 126 - return $.extend(this, newXWikiDocument); 127 - } ).catch(()=>{110 + thisXWikiDocument.isNew = false; 111 + return $.extend(thisXWikiDocument, newXWikiDocument); 112 + }, function() { 128 128 // Reload failed. 129 - return Promise.reject(this);114 + return thisXWikiDocument; 130 130 }); 131 131 }, 132 132 ... ... @@ -137,8 +137,9 @@ 137 137 * @return a promise that resolves to this document instance if the lock request succeeds 138 138 */ 139 139 lock: function(action, force) { 125 + var thisXWikiDocument = this; 140 140 action = action || 'edit'; 141 - return Promise.resolve($.getJSON(this.getURL('get'), {127 + return $.getJSON(this.getURL('get'), { 142 142 sheet: 'XWiki.InplaceEditing', 143 143 action: 'lock', 144 144 lockAction: action, ... ... @@ -147,20 +147,20 @@ 147 147 outputSyntax: 'plain', 148 148 // Make sure the response is not retrieved from cache (IE11 doesn't obey the caching HTTP headers). 149 149 timestamp: new Date().getTime() 150 - }) ).then(()=>{136 + }).then(function() { 151 151 // Lock succeeded. 152 - this.locked = action; 153 - return this; 154 - } ).catch(response=>{138 + thisXWikiDocument.locked = action; 139 + return thisXWikiDocument; 140 + }, function(response) { 155 155 // Lock failed. 156 - delete this.locked; 142 + delete thisXWikiDocument.locked; 157 157 // Check if the user can force the lock. 158 158 var lockConfirmation = response.responseJSON; 159 159 if (response.status === 423 && lockConfirmation) { 160 160 // The user can force the lock, but needs confirmation. 161 - this.lockConfirmation = lockConfirmation; 147 + thisXWikiDocument.lockConfirmation = lockConfirmation; 162 162 } 163 - return Promise.reject(this);149 + return thisXWikiDocument; 164 164 }); 165 165 }, 166 166 ... ... @@ -183,25 +183,6 @@ 183 183 // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests 184 184 $.ajax({type: 'GET', url: url, async: false}); 185 185 } 186 - }, 187 - 188 - /** 189 - * Makes sure this document matches the current UI locale. 190 - */ 191 - translate: function() { 192 - const realLocale = this.getRealLocale(); 193 - const uiLocale = $('html').attr('lang'); 194 - if (realLocale && realLocale !== uiLocale) { 195 - this.language = uiLocale; 196 - // Set the original document locale. 197 - this.translations = this.translations || {}; 198 - this.translations['default'] = realLocale; 199 - // Update the document fields that are not 'shared' with the original document. 200 - this.isNew = true; 201 - delete this.version; 202 - delete this.majorVersion; 203 - delete this.minorVersion; 204 - } 205 205 } 206 206 }; 207 207 }); ... ... @@ -218,7 +218,10 @@ 218 218 'xwiki-events-bridge' 219 219 ], function($, xcontext, xwikiDocumentAPI) { 220 220 var preload = function() { 221 - paths.css.forEach(loadCSS); 188 + loadCSS($jsontool.serialize($xwiki.getSkinFile('js/xwiki/actionbuttons/actionButtons.css', true))); 189 + loadCSS($jsontool.serialize($xwiki.getSkinFile('js/xwiki/editors/autosave.css', true))); 190 + // Required in case the user needs to resolve merge conflicts on save. 191 + loadCSS($jsontool.serialize($xwiki.getSkinFile('uicomponents/viewers/diff.css', true))); 222 222 return initActionButtons(); 223 223 }; 224 224 ... ... @@ -238,12 +238,6 @@ 238 238 }); 239 239 }; 240 240 241 - var translatePage = function() { 242 - return editInPlace({ 243 - afterEdit: createTranslation 244 - }); 245 - }; 246 - 247 247 var editSection = function(sectionId) { 248 248 return editInPlace({ 249 249 lockFailed: function() { ... ... @@ -255,7 +255,7 @@ 255 255 $('#xwikicontent').removeAttr('tabindex'); 256 256 if (sectionId) { 257 257 // Select the heading of the specified section. 258 - $('#xwikicontent > #' + $.escapeSelector(sectionId)).each(function() {222 + $('#xwikicontent > #' + escapeSelector(sectionId)).each(function() { 259 259 selectText(this); 260 260 }); 261 261 } ... ... @@ -263,6 +263,22 @@ 263 263 }); 264 264 }; 265 265 230 + var escapeSelector = function(selector) { 231 + if (window.CSS && typeof CSS.escape === 'function') { 232 + // Not supported by Internet Explorer. 233 + return CSS.escape(selector); 234 + } else if (typeof $.escapeSelector === 'function') { 235 + // Added in jQuery 3.0 236 + return $.escapeSelector(selector); 237 + } else if (typeof selector === 'string') { 238 + // Simple implementation. 239 + // See https://learn.jquery.com/using-jquery-core/faq/how-do-i-select-an-element-by-an-id-that-has-characters-used-in-css-notation/ 240 + return selector.replace(/(:|\.|\[|\]|,|=|@)/g, '\\$1'); 241 + } else { 242 + return selector; 243 + } 244 + }; 245 + 266 266 // We preserve the document data between edits in order to be able to know which document translation should be edited 267 267 // (e.g. when the document translation is missing and we create it, the next edit session should target the created 268 268 // translation). ... ... @@ -270,11 +270,6 @@ 270 270 language: xcontext.locale 271 271 }, xwikiDocumentAPI); 272 272 273 - var setCurrentXWikiDocument = function(xwikiDocument) { 274 - currentXWikiDocument = xwikiDocument; 275 - return Promise.resolve(xwikiDocument); 276 - }; 277 - 278 278 var editInPlace = function(options) { 279 279 options = $.extend({ 280 280 afterEdit: function() {}, ... ... @@ -282,16 +282,11 @@ 282 282 }, options); 283 283 $('#xwikicontent').addClass('loading'); 284 284 // Lock the document first. 285 - return lock(currentXWikiDocument) 260 + return lock(currentXWikiDocument).fail(options.lockFailed) 286 286 // Then load the document only if we managed to lock it. 287 - .then(load, xwikiDocument => { 288 - options.lockFailed(xwikiDocument); 289 - return Promise.reject(xwikiDocument); 262 + .then(load) 290 290 // Then load the editors only if we managed to load the document. 291 - }).then(edit).then(xwikiDocument => { 292 - options.afterEdit(xwikiDocument); 293 - return xwikiDocument; 294 - }).finally(() => { 264 + .then(edit).done(options.afterEdit).always(function() { 295 295 $('#xwikicontent').removeClass('loading'); 296 296 // Then wait for an action (save, cancel, reload) only if the editors were loaded successfuly. 297 297 }).then(maybeSave) ... ... @@ -298,40 +298,31 @@ 298 298 // Then unlock the document both when the edit ended with success and with a failure. 299 299 .then(unlock, unlock) 300 300 // Finally view the document both when the edit ended with success and with a failure. 301 - .then(view, view) 302 - // Update the current document for the next edit session. 303 - .then(setCurrentXWikiDocument, setCurrentXWikiDocument); 271 + .then(view, view); 304 304 }; 305 305 306 306 var lock = function(xwikiDocument) { 307 - return xwikiDocument.lock(). catch(function(xwikiDocument) {275 + return xwikiDocument.lock().then(null, function(xwikiDocument) { 308 308 // If the document was already locked then we need to ask the user if they want to force the lock. 309 309 if (xwikiDocument.lockConfirmation) { 310 310 var confirmation = xwikiDocument.lockConfirmation; 311 311 delete xwikiDocument.lockConfirmation; 312 - return maybeForceLock(confirmation).then(xwikiDocument.lock.bind(xwikiDocument, 'edit', true), function() { 313 - // Cancel the edit action. 314 - return Promise.reject(xwikiDocument); 315 - }); 280 + return maybeForceLock(confirmation).then($.proxy(xwikiDocument, 'lock', 'edit', true)); 316 316 } else { 317 - new XWiki.widgets.Notification(l10n['edit.inplace.page.lockFailed'], 'error'); 318 - return Promise.reject(xwikiDocument); 282 + new XWiki.widgets.Notification( 283 + $jsontool.serialize($services.localization.render('edit.inplace.page.lockFailed')), 284 + 'error' 285 + ); 286 + return xwikiDocument; 319 319 } 320 320 }); 321 321 }; 322 322 323 323 var maybeForceLock = function(confirmation) { 324 - var deferred, promise = new Promise((resolve, reject) => { 325 - deferred = {resolve, reject}; 326 - }); 327 - // We need the catch() to prevent the "Uncaught (in promise)" error log in the console. 328 - promise.catch(() => {}).finally(() => { 329 - // This flag is used by the Force Lock modal to know whether the promise is settled when the modal is closing. 330 - deferred.settled = true; 331 - }); 292 + var deferred = $.Deferred(); 332 332 // Reuse the confirmation modal once it is created. 333 333 var modal = $('.force-edit-lock-modal'); 334 - if ( !modal.length) {295 + if (modal.length === 0) { 335 335 modal = createForceLockModal(); 336 336 } 337 337 // Update the deferred that needs to be resolved or rejected. ... ... @@ -347,7 +347,7 @@ 347 347 } 348 348 // Show the confirmation modal. 349 349 modal.modal('show'); 350 - return promise; 311 + return deferred.promise(); 351 351 }; 352 352 353 353 var createForceLockModal = function() { ... ... @@ -370,17 +370,17 @@ 370 370 '</div>', 371 371 '</div>' 372 372 ].join('')); 373 - modal.find('.close').attr('aria-label', l 10n['edit.inplace.close']);374 - modal.find('.modal-footer .btn-warning'). on('click',function() {334 + modal.find('.close').attr('aria-label', $jsontool.serialize($services.localization.render('edit.inplace.close'))); 335 + modal.find('.modal-footer .btn-warning').click(function() { 375 375 // The user has confirmed they want to force the lock. 376 376 modal.data('deferred').resolve(); 377 377 modal.modal('hide'); 378 378 }); 379 379 modal.on('hide.bs.modal', function() { 380 - // If the lock promise is not yet settled when the modal is closing then it means the modal was canceled,341 + // If the lock promise is not yet resolved when the modal is closing then it means the modal was canceled, 381 381 // i.e. the user doesn't want to force the lock. 382 382 var deferred = modal.data('deferred'); 383 - if ( !deferred.settled) {344 + if (deferred.state() === 'pending') { 384 384 deferred.reject(); 385 385 } 386 386 }); ... ... @@ -388,19 +388,18 @@ 388 388 }; 389 389 390 390 var load = function(xwikiDocument) { 391 - return xwikiDocument.reload(). then(xwikiDocument=>{352 + return xwikiDocument.reload().done(function(xwikiDocument) { 392 392 // Clone the current document version and keep a reference to it in order to be able to restore it on cancel. 393 393 xwikiDocument.originalDocument = $.extend(true, { 394 394 renderedTitle: $('#document-title h1').html(), 395 395 renderedContent: $('#xwikicontent').html() 396 396 }, xwikiDocument); 397 - return xwikiDocument; 398 - }).catch(xwikiDocument => { 399 - new XWiki.widgets.Notification(l10n['edit.inplace.page.loadFailed'], 'error'); 400 - return Promise.reject(xwikiDocument); 358 + }).fail(function() { 359 + new XWiki.widgets.Notification($jsontool.serialize($services.localization.render('edit.inplace.page.loadFailed')), 360 + 'error'); 401 401 // Render the document for edit, in order to have the annotated content HTML. The annotations are used to protect 402 402 // the rendering transformations (e.g. macros) when editing the content. 403 - }).then(render .bind(null, false));363 + }).then($.proxy(render, null, false)); 404 404 }; 405 405 406 406 /** ... ... @@ -413,7 +413,7 @@ 413 413 }; 414 414 415 415 var maybeSave = function(xwikiDocument) { 416 - return waitForAction(xwikiDocument).then(action =>{376 + return waitForAction(xwikiDocument).then(function(action) { 417 417 switch(action.name) { 418 418 case 'save': return save({ 419 419 document: action.document, ... ... @@ -426,29 +426,29 @@ 426 426 }; 427 427 428 428 var waitForAction = function(xwikiDocument) { 429 - return new Promise((resolve, reject) => { 430 - // We wait for the first save, reload or cancel event, whichever is triggered first. Note that the event listeners 431 - // that are not executed first will remain registered but that doesn't cause any problems because the state of a 432 - // deferred object (promise) cannot change once it was resolved. So the first event that fires will resolve the 433 - // promise and the remaining events won't be able to change that. The remaining event listeners could be called 434 - // later but they won't have any effect on the deferred object. 435 - $(document).one([ 436 - 'xwiki:actions:save', 437 - 'xwiki:actions:reload', 438 - 'xwiki:actions:cancel', 439 - ].join(' '), '.xcontent.form', function(event, data) { 440 - resolve({ 441 - name: event.type.substring('xwiki:actions:'.length), 442 - document: xwikiDocument, 443 - data: data 444 - }); 389 + var deferred = $.Deferred(); 390 + // We wait for the first save, reload or cancel event, whichever is triggered first. Note that the event listeners 391 + // that are not executed first will remain registered but that doesn't cause any problems because the state of a 392 + // deferred object (promise) cannot change once it was resolved. So the first event that fires will resolve the 393 + // promise and the remaining events won't be able to change that. The remaining event listeners could be called 394 + // later but they won't have any effect on the deferred object. 395 + $(document).one([ 396 + 'xwiki:actions:save', 397 + 'xwiki:actions:reload', 398 + 'xwiki:actions:cancel', 399 + ].join(' '), function(event, data) { 400 + deferred.resolve({ 401 + name: event.type.substring('xwiki:actions:'.length), 402 + document: xwikiDocument, 403 + data: data 445 445 }); 446 446 }); 406 + return deferred.promise(); 447 447 }; 448 448 449 449 var save = function(data) { 450 450 // Push the changes to the server. 451 - return push(data.document).then(xwikiDocument =>{411 + return push(data.document).then(function(xwikiDocument) { 452 452 // Save succeeded. 453 453 return shouldReload(xwikiDocument).then( 454 454 // The document was saved with merge and thus if we want to continue eding we need to reload the editor (because ... ... @@ -455,7 +455,7 @@ 455 455 // its content doesn't match the saved content). 456 456 reload, 457 457 // No need to reload the editor because either the action was Save & View or there was no merge on save. 458 - maybeContinueEditing .bind(null, data['continue'])418 + $.proxy(maybeContinueEditing, null, data['continue']) 459 459 ); 460 460 // Save failed. Continue editing because we may have unsaved content. 461 461 }, maybeSave); ... ... @@ -463,15 +463,15 @@ 463 463 464 464 var push = function(xwikiDocument) { 465 465 // Let actionButtons.js do the push. We just catch the result. 466 - r eturnnew Promise((resolve,reject)=>{467 - 468 - 469 - 470 - remaining event won't be able to change that. The remaining event listener could be called later but it won't471 - haveany effect.472 - '.xcontent.form', resolve.bind(null, xwikiDocument));473 - '.xcontent.form', reject.bind(null, xwikiDocument));474 - });426 + var deferred = $.Deferred(); 427 + // We wait for the save request to either succeed or fail. Note that one of the event listeners will remain 428 + // registered but that doesn't cause any problems because the state of a deferred object (promise) cannot change 429 + // once it was resolved or rejected. So the first event that fires will resolve/reject the promise and the remaining 430 + // event won't be able to change that. The remaining event listener could be called later but it won't have any 431 + // effect. 432 + $(document).one('xwiki:document:saved', $.proxy(deferred, 'resolve', xwikiDocument)); 433 + $(document).one('xwiki:document:saveFailed', $.proxy(deferred, 'reject', xwikiDocument)); 434 + return deferred.promise(); 475 475 }; 476 476 477 477 var maybeContinueEditing = function(continueEditing, xwikiDocument) { ... ... @@ -492,9 +492,9 @@ 492 492 493 493 // Reload the document JSON data (to have the new version) and render the document for view. We need the view HTML 494 494 // both if we stop editing now and if we continue but cancel the edit later. 495 - return xwikiDocument.reload().then(render .bind(null, true)).then(496 - afterReloadAndRender .bind(null, /* success: */ true),497 - afterReloadAndRender .bind(null, /* success: */ false)455 + return xwikiDocument.reload().then($.proxy(render, null, true)).then( 456 + $.proxy(afterReloadAndRender, null, /* success: */ true), 457 + $.proxy(afterReloadAndRender, null, /* success: */ false) 498 498 ); 499 499 }; 500 500 ... ... @@ -513,25 +513,25 @@ 513 513 }; 514 514 515 515 // Make sure we unlock the document when the user navigates to another page. 516 - $(window).on('unload pagehide', unlock .bind(null, currentXWikiDocument));476 + $(window).on('unload pagehide', $.proxy(unlock, null, currentXWikiDocument)); 517 517 518 518 var shouldReload = function(xwikiDocument) { 519 519 var reloadEventFired = false; 520 - $(document).one('xwiki:actions:reload.maybe', '.xcontent.form', function() {480 + $(document).one('xwiki:actions:reload.maybe', function() { 521 521 reloadEventFired = true; 522 522 }); 523 - r eturnnew Promise((resolve,reject)=>{524 - 525 - 526 - 527 - 528 - 529 - 530 - 531 - 532 - 533 - 534 - });483 + var deferred = $.Deferred(); 484 + // Wait a bit to see if the reload event is fired. 485 + setTimeout(function() { 486 + // Remove the listener in case the reload event wasn't fired. 487 + $(document).off('xwiki:actions:reload.maybe'); 488 + if (reloadEventFired) { 489 + deferred.resolve(xwikiDocument); 490 + } else { 491 + deferred.reject(xwikiDocument); 492 + } 493 + }, 0); 494 + return deferred.promise(); 535 535 }; 536 536 537 537 var reload = function(xwikiDocument) { ... ... @@ -540,33 +540,21 @@ 540 540 }; 541 541 542 542 var view = function(xwikiDocument, reload) { 543 - var viewContent = $('#xwikicontent'); 544 544 // Destroy the editors before returning to view. 545 - viewContent.trigger('xwiki:actions:view', {document: xwikiDocument});504 + $(document).trigger('xwiki:actions:view', {document: xwikiDocument}); 546 546 $('#document-title h1').html(xwikiDocument.renderedTitle); 547 - viewContent.html(xwikiDocument.renderedContent);506 + $('#xwikicontent').html(xwikiDocument.renderedContent); 548 548 if (!reload) { 549 549 // If the user has canceled the edit then the restored page content may include the section edit links. Show them 550 550 // in case they were hidden. 551 - viewContent.children(':header').children('.edit_section').removeClass('hidden');510 + $('#xwikicontent').children(':header').children('.edit_section').removeClass('hidden'); 552 552 // Let others know that the DOM has been updated, in order to enhance it. 553 - $(document).trigger('xwiki:dom:updated', {'elements': viewContent.toArray()});512 + $(document).trigger('xwiki:dom:updated', {'elements': $('#xwikicontent').toArray()}); 554 554 } 555 - // Remove the action events scope. 556 - viewContent.closest('.form').removeClass('form'); 557 - // Update the URL. 558 - if (window.location.hash === '#edit' || window.location.hash === '#translate') { 559 - history.replaceState(null, null, '#'); 560 - } 561 - return Promise.resolve(xwikiDocument); 514 + return $.Deferred().resolve(xwikiDocument).promise(); 562 562 }; 563 563 564 564 var edit = function(xwikiDocument) { 565 - // By adding the 'form' CSS class we set the scope of the action events (e.g. xwiki:actions:beforeSave or 566 - // xwiki:actions:cancel). We need this because in view mode we can have multiple forms active on the page (e.g. one 567 - // for editing the document content in place and one for editing the document syntax in-place) and we don't want 568 - // them to interfere (e.g. canceling one form shouldn't cancel the other forms). 569 - $('#xwikicontent').closest('.xcontent').addClass('form'); 570 570 return initActionButtons(xwikiDocument).then(initTitleEditor).then(initContentEditor) 571 571 .then(startRealTimeEditingSession); 572 572 }; ... ... @@ -573,7 +573,7 @@ 573 573 574 574 var initActionButtons = function(xwikiDocument) { 575 575 if (xwikiDocument) { 576 - initTranslateButton(xwikiDocument);524 + maybeShowTranslateButton(xwikiDocument); 577 577 } 578 578 var editContent = $('#xwikicontent'); 579 579 // We need the wrapper because #xwikicontent uses Bootstrap grid (col-xs-12) which is implemented with CSS float. ... ... @@ -583,90 +583,67 @@ 583 583 var actionButtonsWrapper = editContent.nextAll('.sticky-buttons-wrapper'); 584 584 if (actionButtonsWrapper.length === 0) { 585 585 actionButtonsWrapper = $('<div class="sticky-buttons-wrapper col-xs-12">' + 586 - '<div class="inplace-editing-buttons sticky-buttons"/></div>').insertAfter(editContent).toggle(!!xwikiDocument); 587 - var actionButtons = actionButtonsWrapper.children('.sticky-buttons') 588 - .data('xwikiDocument', xwikiDocument) 589 - // Expose the fake form if an extension needs to manipulate it. 590 - .data('fakeForm', fakeForm); 534 + '<div class="inplace-editing-buttons sticky-buttons"/></div>').insertAfter(editContent); 535 + var actionButtons = actionButtonsWrapper.children('.sticky-buttons').data('xwikiDocument', xwikiDocument) 536 + .toggle(!!xwikiDocument); 591 591 return loadActionButtons(actionButtons); 592 592 } else { 593 593 // If we're editing a page.. 594 594 if (xwikiDocument) { 595 595 // ..then make sure the action buttons are displayed right away (don't wait for the user to scroll). 596 - actionButtonsWrapper.show().children('.sticky-buttons') 597 - .data('xwikiDocument', xwikiDocument) 598 - // Expose the fake form if an extension needs to manipulate it. 599 - .data('fakeForm', fakeForm) 600 - // but make sure the position of the action buttons is updated. 601 - .trigger('xwiki:dom:refresh'); 542 + actionButtonsWrapper.children('.sticky-buttons').data('xwikiDocument', xwikiDocument).show(); 602 602 // The action buttons are disabled on Save & View. We don't reload the page on Save & View and we reuse the 603 603 // action buttons so we need to re-enable them each time we enter the edit mode. 604 604 fakeForm.enable(); 546 + $(document).trigger('xwiki:dom:refresh'); 605 605 } 606 - return Promise.resolve(xwikiDocument);548 + return $.Deferred().resolve(xwikiDocument).promise(); 607 607 } 608 608 }; 609 609 610 - var createTranslation = function(xwikiDocument) { 611 - xwikiDocument.translate(); 612 - $('#document-title-input').focus().select(); 613 - // Let the user know that they are now editing the translation of this page in the current locale. 614 - $('#document-title-input').popover({ 615 - content: l10n['edit.inplace.page.translate.messageAfter'], 616 - placement: 'bottom', 617 - trigger: 'manual' 618 - }).popover('show').one('blur', function() { 619 - // Hide the popover when the title input loses the focus. 620 - $(this).popover('hide'); 621 - }); 552 + var maybeShowTranslateButton = function(xwikiDocument) { 553 + var xwikiDocumentLocale = xwikiDocument.getRealLocale(); 554 + var uiLocale = $('html').attr('lang'); 555 + if (xwikiDocumentLocale && xwikiDocumentLocale !== uiLocale) { 556 + $('#tmTranslate').off('click.translate').on('click.translate', function(event) { 557 + event.preventDefault(); 558 + $(this).addClass('hidden'); 559 + xwikiDocument.language = uiLocale; 560 + // Update the document translation fields that are not 'shared' with the original document. 561 + xwikiDocument.isNew = true; 562 + delete xwikiDocument.version; 563 + delete xwikiDocument.majorVersion; 564 + delete xwikiDocument.minorVersion; 565 + $('#document-title-input').focus().select(); 566 + var message = $jsontool.serialize($services.localization.render('edit.inplace.page.translation', 567 + ['__locale__'])); 568 + new XWiki.widgets.Notification( 569 + message.replace('__locale__', uiLocale), 570 + 'info' 571 + ); 572 + }).removeClass('hidden'); 573 + var message = $jsontool.serialize($services.localization.render('edit.inplace.page.original', ['__locale__'])); 574 + new XWiki.widgets.Notification( 575 + message.replace('__locale__', xwikiDocumentLocale), 576 + 'info' 577 + ); 578 + } 622 622 }; 623 623 624 - var initTranslateButton = function(xwikiDocument) { 625 - // Initialize the translate button only if it's visible. 626 - const translateButton = $(config.translateButtonSelector).filter('[data-toggle="popover"]').filter(':visible'); 627 - translateButton.off('click.translate').on('click.translate', function(event) { 628 - event.preventDefault(); 629 - translateButton.parent().addClass('hidden'); 630 - createTranslation(xwikiDocument); 631 - // Let the user know that they are editing the original version of the page and not the translation corresponding 632 - // to the current locale because there isn't one created yet. 633 - }).attr({ 634 - // Backup the initial popover message to be able to restore it on view. 635 - 'data-content-view': translateButton.attr('data-content'), 636 - // Use a custom popover message dedicated to the edit action. 637 - 'data-content': l10n['edit.inplace.page.translate.messageBefore'] 638 - }).popover('show') 639 - // Hide the popover on the next click. The user can still see the message by hovering the translate button. 640 - .closest('html').one('click', function() { 641 - translateButton.popover('hide'); 642 - }); 643 - }; 644 - 645 645 var loadActionButtons = function(actionButtons) { 646 - $(document).on('xwiki:actions:view', '.xcontent.form', function(event, data) {582 + $(document).on('xwiki:actions:view', function() { 647 647 // Blur the action buttons first to re-enable the "disabled in inputs" shortcut keys (e.g. the page edit 648 648 // shortcut), then disable the action buttons in order to disable their shortcut keys while we're not editing 649 649 // in-place (e.g. prevent the Save shortcut while the user is only viewing the page). Finally hide the action 650 650 // buttons to have them ready for the next editing session (the user can save or cancel and then edit again 651 651 // without reloading the page). 652 - actionButtons.find(':input').blur().prop('disabled', true).end().parent().hide(); 653 - // Restore the Translate button if the locale of the viewed document doesn't match the current user interface 654 - // locale (because the viewed document doesn't have a translation in the current locale). 655 - var xwikiDocumentLocale = data.document.getRealLocale(); 656 - var uiLocale = $('html').attr('lang'); 657 - if (xwikiDocumentLocale && xwikiDocumentLocale !== uiLocale) { 658 - const translateButton = $(config.translateButtonSelector).filter('[data-toggle="popover"]'); 659 - // Restore the translation button behavior for view action. 660 - translateButton.off('click.translate') 661 - // Restore the popover text for view action. 662 - .attr('data-content', translateButton.attr('data-content-view') || translateButton.attr('data-content')) 663 - // Restore the visibility. 664 - .parent().removeClass('hidden'); 665 - } 588 + actionButtons.find(':input').blur().prop('disabled', true).end().hide(); 589 + // Hide the translate button because it can be used only in edit mode for the moment. 590 + $('#tmTranslate').addClass('hidden'); 666 666 }); 667 - return Promise.resolve($.get(XWiki.currentDocument.getURL('get'), {592 + return $.get(XWiki.currentDocument.getURL('get'), { 668 668 xpage: 'editactions' 669 - }) ).then(html=>{594 + }).then(function(html) { 670 670 actionButtons.html(html); 671 671 // Fix the name of the Save & View action. 672 672 actionButtons.find('.btn-primary').first().attr('name', 'action_save'); ... ... @@ -674,24 +674,25 @@ 674 674 $('<input type="hidden" name="form_token" />').val(xcontext.form_token).appendTo(actionButtons); 675 675 // We need a place where actionButtons.js can add more hidden inputs. 676 676 actionButtons.append('<div class="hidden extra"/>'); 677 - // Let the others know that the DOM has been updated, in order to enhance it. 678 - $(document).trigger('xwiki:dom:updated', {'elements': actionButtons.toArray()}); 679 - return new Promise((resolve, reject) => { 680 - require(['xwiki-actionButtons', 'xwiki-diff', 'xwiki-autoSave'], function() { 681 - overrideEditActions(); 682 - overrideAjaxSaveAndContinue(); 683 - // Activate the auto-save feature passing our fake edit form. Note that autosave.js also creates an instance of 684 - // AutoSave but it doesn't do anything because it doesn't find a real edit form in the page. This is why we have 685 - // to create our own instance of AutoSave passing the right (fake) form. 686 - new XWiki.editors.AutoSave({form: fakeForm}); 687 - var xwikiDocument = actionButtons.data('xwikiDocument'); 688 - // Enable the action buttons (and their shortcut keys) only if we're editing a document. 689 - actionButtons.find(':input').prop('disabled', !xwikiDocument); 690 - resolve(xwikiDocument); 691 - }); 602 + var deferred = $.Deferred(); 603 + require(['actionButtons', 'xwiki-diff', 'autoSave'], function() { 604 + overrideEditActions(); 605 + overrideAjaxSaveAndContinue(); 606 + // Activate the auto-save feature passing our fake edit form. Note that autosave.js also creates an instance of 607 + // AutoSave but it doesn't do anything because it doesn't find a real edit form in the page. This is why we have 608 + // to create our own instance of AutoSave passing the right (fake) form. 609 + new XWiki.editors.AutoSave({form: fakeForm}); 610 + var xwikiDocument = actionButtons.data('xwikiDocument'); 611 + // Enable the action buttons (and their shortcut keys) only if we're editing a document. 612 + actionButtons.find(':input').prop('disabled', !xwikiDocument); 613 + deferred.resolve(xwikiDocument); 692 692 }); 693 - }).catch(() => { 694 - new XWiki.widgets.Notification(l10n['edit.inplace.actionButtons.loadFailed'], 'error'); 615 + return deferred.promise(); 616 + }, function() { 617 + new XWiki.widgets.Notification( 618 + $jsontool.serialize($services.localization.render('edit.inplace.actionButtons.loadFailed')), 619 + 'error' 620 + ); 695 695 }); 696 696 }; 697 697 ... ... @@ -717,10 +717,6 @@ 717 717 insert: function(element) { 718 718 this._getActionButtons().find('.hidden.extra').append(element); 719 719 }, 720 - // Note that this method only works with single argument. 721 - append: function(element) { 722 - this.insert(element); 723 - }, 724 724 down: function(selector) { 725 725 return this._getActionButtons().find(selector)[0]; 726 726 }, ... ... @@ -731,13 +731,6 @@ 731 731 extra[entry.name] = value; 732 732 return extra; 733 733 }, {}); 734 - // retrieve all input fields listing the temporary uploaded files. 735 - var uploadedFiles = $('#xwikicontent').nextAll('input[name="uploadedFiles"]').serializeArray().reduce(function(extra, entry) { 736 - var value = extra[entry.name] || []; 737 - value.push(entry.value); 738 - extra[entry.name] = value; 739 - return extra; 740 - }, {}); 741 741 var xwikiDocument = this._getActionButtons().data('xwikiDocument'); 742 742 var formData = { 743 743 title: xwikiDocument.rawTitle, ... ... @@ -755,15 +755,13 @@ 755 755 content_syntax: xwikiDocument.syntax 756 756 }); 757 757 } 758 - // Add the temporary uploaded files to the form. 759 - $.extend(formData, uploadedFiles); 760 760 // Check for merge conflicts only if the document is not new and we know the current version. 761 761 if (!xwikiDocument.isNew && xwikiDocument.version) { 762 762 formData.previousVersion = xwikiDocument.version; 763 - formData.editingVersionDate = new Date(xwikiDocument.modified).getTime(); 676 + // It would have been easier to send the timestamp but that's what the Save action expects. 677 + formData.editingVersionDate = new Date(xwikiDocument.modified).toISOString(); 764 764 } 765 - // Ensure that formData information has priority over extra information. 766 - return $.extend({}, extra, formData); 679 + return $.extend(formData, extra); 767 767 } 768 768 }; 769 769 ... ... @@ -785,9 +785,8 @@ 785 785 var originalAjaxSaveAndContinue = $.extend({}, XWiki.actionButtons.AjaxSaveAndContinue.prototype); 786 786 $.extend(XWiki.actionButtons.AjaxSaveAndContinue.prototype, { 787 787 reloadEditor: function() { 788 - var actionButtons = $('.inplace-editing-buttons'); 789 - if (actionButtons.is(':visible')) { 790 - actionButtons.trigger('xwiki:actions:reload'); 701 + if ($('.inplace-editing-buttons').is(':visible')) { 702 + $(document).trigger('xwiki:actions:reload'); 791 791 } else { 792 792 return originalAjaxSaveAndContinue.reloadEditor.apply(this, arguments); 793 793 } ... ... @@ -808,7 +808,7 @@ 808 808 809 809 var initTitleEditor = function(xwikiDocument) { 810 810 var label = $('<label for="document-title-input" class="sr-only"/>') 811 - .text(l 10n['core.editors.content.titleField.label']);723 + .text($jsontool.serialize($services.localization.render('core.editors.content.titleField.label'))); 812 812 var input = $('<input type="text" id="document-title-input"/>').val(xwikiDocument.rawTitle); 813 813 var placeholder = xwikiDocument.documentReference.name; 814 814 if (placeholder === 'WebHome') { ... ... @@ -816,10 +816,10 @@ 816 816 } 817 817 input.attr('placeholder', placeholder); 818 818 $('#document-title h1').addClass('editable').empty().append([label, input]); 819 - $(document).on('xwiki:actions:beforeSave.titleEditor', '.xcontent.form', function(event) {731 + $(document).on('xwiki:actions:beforeSave.titleEditor', function(event) { 820 820 xwikiDocument.rawTitle = input.val(); 821 821 }); 822 - $(document).one('xwiki:actions:view', '.xcontent.form', function(event, data) {734 + $(document).one('xwiki:actions:view', function(event, data) { 823 823 // Destroy the title editor. 824 824 $(document).off('xwiki:actions:beforeSave.titleEditor'); 825 825 $('#document-title h1').removeClass('editable').text(xwikiDocument.rawTitle); ... ... @@ -839,25 +839,28 @@ 839 839 // Keep the focus while the edit content is being prepared. 840 840 viewContent.focus(); 841 841 } 842 - var data = $.extend({}, config, { 754 + var data = { 755 + contentType: 'org.xwiki.rendering.syntax.SyntaxContent', 756 + editMode: 'wysiwyg', 843 843 document: xwikiDocument, 844 844 // The content editor is loaded on demand, asynchronously. 845 - deferred: $.Deferred() 846 - }); 847 - editContent.trigger('xwiki:actions:edit', data); 848 - return data.deferred.promise().then(() => { 759 + deferred: $.Deferred(), 760 + // We have to explicitly enable the source mode for in-line edit because the latest version of the content editor 761 + // could be installed on an older version of XWiki where the in-place editor didn't support the source mode (so 762 + // the content editor cannot enable the source mode by default). 763 + enableSourceMode: true 764 + }; 765 + var editContentPromise = data.deferred.promise(); 766 + editContentPromise.done(function() { 849 849 editContent.show(); 850 850 viewContent.remove(); 851 851 if (withFocus) { 852 - // Restore the focus when the edit content is ready but make sure we don't scroll the page. We don't restore the 853 - // focus right away because we just made the content visible so it may not be editable yet (e.g. the WYSIWYG 854 - // editor can make the content editable only if it is visible). 855 - setTimeout(function() { 856 - editContent[0].focus({preventScroll: true}); 857 - }, 0); 770 + // Restore the focus when the edit content is ready but make sure we don't scroll the page. 771 + editContent[0].focus({preventScroll: true}); 858 858 } 859 - return xwikiDocument; 860 860 }); 774 + editContent.trigger('xwiki:actions:edit', data); 775 + return editContentPromise; 861 861 }; 862 862 863 863 var startRealTimeEditingSession = function(xwikiDocument) { ... ... @@ -876,10 +876,9 @@ 876 876 }; 877 877 878 878 return { 879 - preload, 880 - editPage, 881 - editSection, 882 - translatePage 794 + preload: preload, 795 + editPage: editPage, 796 + editSection: editSection 883 883 }; 884 884 }); 885 885 ... ... @@ -889,7 +889,8 @@ 889 889 return; 890 890 } 891 891 892 - var wysiwygEditorModule = 'xwiki-' + config.wysiwygEditor + '-inline'; 806 + var inplaceEditingConfig = $('div[data-inplace-editing-config]').data('inplaceEditingConfig') || {}; 807 + var wysiwygEditorModule = 'xwiki-' + inplaceEditingConfig.wysiwygEditor + '-inline'; 893 893 894 894 var preloadEditor = function() { 895 895 require(['editInPlace', wysiwygEditorModule], function(editInPlace) { ... ... @@ -907,186 +907,44 @@ 907 907 }); 908 908 } 909 909 910 - var onInPlaceEditing = function(event) { 825 + var editButton = $('#tmEdit > a'); 826 + editButton.on('click.inPlaceEditing', function(event) { 827 + event.preventDefault(); 911 911 // Make sure the user doesn't try to re-activate the edit mode while we are in edit mode. 912 - if (editButton.hasClass('disabled')) { 913 - return; 914 - } 915 - // Disable the edit buttons and hide the section edit links. 916 - editButton.add(translateButton).addClass('disabled'); 917 - $('#xwikicontent').children(':header').children('.edit_section').addClass('hidden'); 829 + editButton.addClass('disabled'); 830 + // Load the code needed to edit in place only when the edit button is clicked. 831 + require(['editInPlace', wysiwygEditorModule], function(editInPlace) { 832 + editInPlace.editPage().always(function() { 833 + editButton.removeClass('disabled'); 834 + }); 835 + // Fallback on the standalone edit mode if we fail to load the required modules. 836 + }, $.proxy(disableInPlaceEditing, event.target)); 837 + }); 838 + 839 + // Section in-place editing. 840 + $('#xwikicontent').on('click.inPlaceEditing', '> :header > a.edit_section:not(.disabled)', function(event) { 918 918 event.preventDefault(); 919 - const handler = event.data; 920 - const data = handler.beforeEdit?.(event); 842 + // Make sure the user doesn't try to re-activate the edit mode while we are in edit mode. 843 + editButton.addClass('disabled'); 844 + // Hide the section editing links and focus the content right away. We could have replaced the section editing icon 845 + // with a loading animation / spinner but giving instant visual feedback about what is going to happen is perceived 846 + // better by the users (it feels faster). 847 + $('#xwikicontent').attr('tabindex', '0').focus().children(':header').children('.edit_section').addClass('hidden'); 848 + var heading = $(event.target).closest(':header'); 921 921 // Load the code needed to edit in place only when the edit button is clicked. 922 922 require(['editInPlace', wysiwygEditorModule], function(editInPlace) { 923 - // Re-enable the translate button because it can be used while editing to create the missing translation. 924 - translateButton.removeClass('disabled'); 925 - handler.edit(editInPlace, data).finally(function() { 926 - // Restore only the edit button at the end because: 927 - // * the translate button is restored (if needed) by the editInPlace module 928 - // * the section edit links are restored when the document is rendered for view 851 + editInPlace.editSection(heading.attr('id')).always(function() { 929 929 editButton.removeClass('disabled'); 930 930 }); 931 931 // Fallback on the standalone edit mode if we fail to load the required modules. 932 - }, disableInPlaceEditing .bind(event.target));933 - }; 855 + }, $.proxy(disableInPlaceEditing, event.target)); 856 + }); 934 934 935 935 var disableInPlaceEditing = function() { 936 - editButton. add(translateButton).off('click.inPlaceEditing').removeClass('disabled');859 + editButton.off('click.inPlaceEditing').removeClass('disabled'); 937 937 $('#xwikicontent').off('click.inPlaceEditing').removeAttr('tabindex').children(':header').children('.edit_section') 938 938 .removeClass('hidden'); 939 939 // Fallback on the standalone edit mode. 940 940 $(this).click(); 941 941 }; 942 - 943 - var editButton = $(config.editButtonSelector); 944 - editButton.on('click.inPlaceEditing', { 945 - beforeEdit: function() { 946 - history.replaceState(null, null, '#edit'); 947 - }, 948 - edit: function(editInPlace) { 949 - return editInPlace.editPage(); 950 - } 951 - }, onInPlaceEditing).attr('data-editor', 'inplace'); 952 - 953 - var translateButton = $(config.translateButtonSelector); 954 - translateButton.on('click.inPlaceEditing', { 955 - beforeEdit: function() { 956 - history.replaceState(null, null, '#translate'); 957 - translateButton.parent().addClass('hidden'); 958 - }, 959 - edit: function(editInPlace) { 960 - return editInPlace.translatePage(); 961 - } 962 - }, onInPlaceEditing); 963 - 964 - // Section in-place editing. 965 - $('#xwikicontent').on('click.inPlaceEditing', '> :header > a.edit_section:not(.disabled)', { 966 - beforeEdit: function(event) { 967 - // Focus the content right away to give the user instant visual feedback about what is going to happen. 968 - $('#xwikicontent').attr('tabindex', '0').focus(); 969 - // Return the id of the edited section. 970 - return $(event.target).closest(':header').attr('id'); 971 - }, 972 - edit: function(editInPlace, sectionId) { 973 - return editInPlace.editSection(sectionId); 974 - } 975 - }, onInPlaceEditing); 976 - 977 - if (window.location.hash === '#edit') { 978 - editButton.click(); 979 - } else if (window.location.hash === '#translate') { 980 - translateButton.click(); 981 - } 982 982 }); 983 - 984 -require(['jquery'], function($) { 985 - // Backup the document title before each editing session in order to catch changes. 986 - var previousPlainTitle; 987 - $('#xwikicontent').on('xwiki:actions:edit', function(event, data) { 988 - previousPlainTitle = data.document.getPlainTitle(); 989 - }); 990 - 991 - // Update the UI after each editing session. 992 - $(document).on('xwiki:actions:view', function(event, data) { 993 - var xwikiDocument = data.document; 994 - updateDocAuthorAndDate(xwikiDocument); 995 - updateDocExtraTabs(xwikiDocument); 996 - updateDrawer(xwikiDocument); 997 - updateContentMenu(xwikiDocument); 998 - if (xwikiDocument.getPlainTitle() !== previousPlainTitle) { 999 - updateDocTrees(xwikiDocument); 1000 - updateLinks(xwikiDocument); 1001 - } 1002 - }); 1003 - 1004 - var updateDocAuthorAndDate = function(xwikiDocument) { 1005 - var urlWithSelector = xwikiDocument.getURL('get', 'xpage=contentheader') + ' .xdocLastModification'; 1006 - $('.xdocLastModification').load(urlWithSelector, function() { 1007 - // load() replaces the content of the specified container but we want to replace the container itself. We can't do 1008 - // this from the selector, e.g. by using '.xdocLastModification > *' because we lose the text nodes. 1009 - $(this).children().unwrap(); 1010 - }); 1011 - }; 1012 - 1013 - var updateDocExtraTabs = function(xwikiDocument) { 1014 - // Reload the selected tab and force the reload of the hidden tabs next time they are selected. 1015 - $('#docextrapanes').children().addClass('empty').empty(); 1016 - var selectedTab = $('#docExtraTabs .active[data-template]'); 1017 - if (selectedTab.length) { 1018 - var docExtraId = selectedTab.attr('id'); 1019 - docExtraId = docExtraId.substring(0, docExtraId.length - 'tab'.length); 1020 - XWiki.displayDocExtra(docExtraId, selectedTab.data('template'), false); 1021 - } 1022 - }; 1023 - 1024 - // Update the document trees (e.g. breadcrumb, navigation) if they have nodes that correspond to the edited document. 1025 - // Note that we want to update the internal tree data not just the link label. This is especially useful if we're 1026 - // going to implement refactoring operations (rename) in the document tree. 1027 - var updateDocTrees = function(xwikiDocument) { 1028 - var plainTitle = xwikiDocument.getPlainTitle(); 1029 - $('.jstree-xwiki').each(function() { 1030 - $(this).jstree?.(true)?.set_text?.('document:' + xwikiDocument.id, plainTitle); 1031 - }); 1032 - }; 1033 - 1034 - // Update the links that target the edited document and whose label matches the document title. Note that this can 1035 - // update links whose label was not generated dynamically (e.g. with server side scripting) based on the document 1036 - // title. For instance there could be links with hard-coded labels or with labels generated using a translatin key 1037 - // (like in the Applications panel). For simplicity we assume that if the link matches the document URL and its 1038 - // previous title then it needs to be updated, but this happens only at the UI level. 1039 - var updateLinks = function(xwikiDocument) { 1040 - var docURL = xwikiDocument.getURL(); 1041 - var newPlainTitle = xwikiDocument.getPlainTitle(); 1042 - // Exclude the links from the document content. 1043 - // Update the links that contain only text (no child elements) otherwise we can lose UI elements (e.g. icons). 1044 - $('a').not('#xwikicontent a').not(':has(*)').filter(function() { 1045 - var linkURL = $(this).attr('href')?.split(/[?#]/, 1)[0]; 1046 - return linkURL === docURL && $(this).text() === previousPlainTitle; 1047 - }).text(newPlainTitle); 1048 - }; 1049 - 1050 - // Update the list of available document translations in the drawer menu. This is needed for instance when a new 1051 - // translation is created using the in-place editor. 1052 - var updateDrawer = function(xwikiDocument) { 1053 - var languageMenu = $('#tmLanguages_menu'); 1054 - var locale = xwikiDocument.getRealLocale(); 1055 - // Look for the language query string parameter, either inside or at the end. 1056 - var localeSelector = 'a[href*="language=' + locale + '&"], a[href$="language=' + locale + '"]'; 1057 - // Check if the language menu is present (multilingual is on) and the document locale is not listed. 1058 - if (languageMenu.length && !languageMenu.find(localeSelector).length) { 1059 - // If we get here then it means a new document translation was created and it needs to be listed in the drawer. 1060 - $('<div/>').load(xwikiDocument.getURL('get', $.param({ 1061 - 'xpage': 'xpart', 1062 - 'vm': 'drawer.vm', 1063 - 'useLayoutVars': true 1064 - // Pass the query string from the current URL so that it gets included in the translation URL. 1065 - // XWIKI-11314: Changing the current language from the UI does not preserve the query string of the current URL 1066 - })) + '&' + location.search.substring(1) + ' #tmLanguages_menu', function() { 1067 - $(this).find('a').each(function() { 1068 - // Clean the query string. 1069 - $(this).attr('href', $(this).attr('href').replace(/&?(xpage=xpart|vm=drawer\.vm|useLayoutVars=true)/g, '') 1070 - .replace('?&', '?')); 1071 - }); 1072 - languageMenu.replaceWith($(this).children()); 1073 - }); 1074 - } 1075 - }; 1076 - 1077 - // Update the links from the content menu to point to the real document locale. This is needed especially when a new 1078 - // document translation is created in-place. 1079 - var updateContentMenu = function(xwikiDocument) { 1080 - var realLocale = xwikiDocument.getRealLocale(); 1081 - var defaultLocale = xwikiDocument.getDefaultLocale(); 1082 - if (realLocale != defaultLocale) { 1083 - var defaultLocaleRegex = new RegExp('(\\blanguage=)' + defaultLocale + '($|&|#)'); 1084 - $('#contentmenu a[href*="language=' + defaultLocale + '"]').each(function() { 1085 - $(this).attr('href', $(this).attr('href').replace(defaultLocaleRegex, '$1' + realLocale + '$2')); 1086 - }); 1087 - } 1088 - }; 1089 -}); 1090 - 1091 -})(JSON.parse(document.querySelector('[data-inplace-editing-config]')?.getAttribute('data-inplace-editing-config')) || 1092 - {}); - Parse content
-
... ... @@ -1,1 +1,1 @@ 1 - No1 +Yes
- XWiki.UIExtensionClass[0]
-
- Executed Content
-
... ... @@ -1,11 +9,3 @@ 1 -{{velocity output="false"}} 2 -## TODO: Remove this when XWIKI-18511 (Add support for passing a query string when calling getSkinFile) is implemented. 3 -#macro (getSkinFileWithParams $file $params) 4 -#set ($url = $xwiki.getSkinFile($file, true)) 5 -$url#if ($url.contains('?'))&#else?#end$escapetool.url($params) 6 -#end 7 -{{/velocity}} 8 - 9 9 {{velocity}} 10 10 {{html clean="false"}} 11 11 #if ($services.edit.document.inPlaceEditingEnabled() && $hasEdit && $xcontext.action == 'view' && !$doc.isNew()) ... ... @@ -12,55 +12,9 @@ 12 12 ## We support in-place editing only for the WYSIWYG edit mode ATM. 13 13 #getDefaultDocumentEditor($defaultEditMode) 14 14 #if ($defaultEditMode == 'wysiwyg') 15 - #set ($l10nKeys = [ 16 - 'edit.inplace.page.renderFailed', 17 - 'edit.inplace.page.lockFailed', 18 - 'edit.inplace.close', 19 - 'edit.inplace.page.loadFailed', 20 - 'edit.inplace.actionButtons.loadFailed', 21 - 'core.editors.content.titleField.label', 22 - ['edit.inplace.page.translate.messageBefore', $doc.realLocale.getDisplayName($xcontext.locale), 23 - $xcontext.locale.getDisplayName($xcontext.locale)], 24 - ['edit.inplace.page.translate.messageAfter', $xcontext.locale.getDisplayName($xcontext.locale)] 25 - ]) 26 - #set ($l10n = {}) 27 - #foreach ($key in $l10nKeys) 28 - #set ($params = $key.subList(1, $key.size())) 29 - #if ($params) 30 - #set ($discard = $l10n.put($key[0], $services.localization.render($key[0], $params))) 31 - #else 32 - #set ($discard = $l10n.put($key, $services.localization.render($key))) 33 - #end 34 - #end 35 - ## See stylesheets.vm 36 - #set ($cssParams = { 37 - 'skin': $xwiki.skin, 38 - 'colorTheme': $services.model.serialize($themeDoc.documentReference, 'default') 39 - }) 40 - #set ($jsParams = {'language': $xcontext.locale}) 41 - ## We have to explicitly enable the source mode for in-line edit because the latest version of the content editor 42 - ## could be installed on an older version of XWiki where the in-place editor didn't support the source mode (so the 43 - ## content editor cannot enable the source mode by default). 44 44 #set ($inplaceEditingConfig = { 45 - 'contentType': 'org.xwiki.rendering.syntax.SyntaxContent', 46 46 'editMode': $defaultEditMode, 47 - 'wysiwygEditor': $services.edit.syntaxContent.defaultWysiwygEditor.descriptor.id, 48 - 'editButtonSelector': '#tmEdit > a', 49 - 'translateButtonSelector': '#tmTranslate > a', 50 - 'enableSourceMode': true, 51 - 'paths': { 52 - 'js': { 53 - 'xwiki-actionButtons': "#getSkinFileWithParams('js/xwiki/actionbuttons/actionButtons.js' $jsParams)", 54 - 'xwiki-autoSave': "#getSkinFileWithParams('js/xwiki/editors/autosave.js' $jsParams)", 55 - 'xwiki-diff': $xwiki.getSkinFile('uicomponents/viewers/diff.js') 56 - }, 57 - 'css': [ 58 - "#getSkinFileWithParams('js/xwiki/actionbuttons/actionButtons.css' $cssParams)", 59 - "#getSkinFileWithParams('js/xwiki/editors/autosave.css' $cssParams)", 60 - "#getSkinFileWithParams('uicomponents/viewers/diff.css' $cssParams)" 61 - ] 62 - }, 63 - 'l10n': $l10n 9 + 'wysiwygEditor': $services.edit.syntaxContent.defaultWysiwygEditor.descriptor.id 64 64 }) 65 65 <div class="hidden" data-inplace-editing-config="$escapetool.xml($jsontool.serialize($inplaceEditingConfig))"></div> 66 66 ## We didn't move this to the file system because it uses LESS and we didn't want to include it in the skin.
- XWiki.UIExtensionClass[1]
-
- Cached
-
... ... @@ -1,0 +1,1 @@ 1 +No - Asynchronous rendering
-
... ... @@ -1,0 +1,1 @@ 1 +No - Executed Content
-
... ... @@ -1,0 +1,22 @@ 1 +{{velocity}} 2 +{{html clean="false"}} 3 +## Output the translation button if all the following conditions are met: 4 +## * multilingual is on 5 +## * we're loading the original document version 6 +## * the original document version has a locale specified (it doesn't make sense to translate technical documents) 7 +## * the current UI locale doesn't match the original document locale 8 +#if ($services.edit.document.inPlaceEditingEnabled() && $hasEdit && $xwiki.isMultiLingual() 9 + && $tdoc.realLocale == $doc.realLocale && "$!doc.realLocale" != '' && $doc.realLocale != $xcontext.locale) 10 + #set ($url = $doc.getURL('edit', $escapetool.url({'language': $xcontext.locale}))) 11 + #set ($hint = $services.localization.render('edit.inplace.page.translate.hint', 12 + [$xcontext.locale.getDisplayName($xcontext.locale)])) 13 + ## We show the translate button only while editing in-place. 14 + <div class="btn-group hidden" id="tmTranslate"> 15 + <a class="btn btn-default" href="$url" role="button" title="$escapetool.xml($hint)"> 16 + $services.icon.renderHTML('translate') 17 + <span class="btn-label">$escapetool.xml($services.localization.render('edit.inplace.page.translate'))</span> 18 + </a> 19 + </div> 20 +#end 21 +{{/html}} 22 +{{/velocity}} - Extension Point ID
-
... ... @@ -1,0 +1,1 @@ 1 +org.xwiki.plaftorm.menu.content - Extension ID
-
... ... @@ -1,0 +1,1 @@ 1 +org.xwiki.plaftorm.menu.content.translate - Extension Parameters
-
... ... @@ -1,0 +1,1 @@ 1 +order=5000 - Extension Scope
-
... ... @@ -1,0 +1,1 @@ 1 +wiki