WRMCB=function(e){var c=console;if(c&&c.log&&c.error){c.log('Error running batched script.');c.error(e);}} ; try { /* module-key = 'com.atlassian.confluence.plugins.reliablesave:reliable-save-conf-frontend', location = 'js/reliable-save.js' */ define('confluence-editor-reliable-save/reliable-save', [ 'ajs', 'confluence/templates', 'underscore', 'jquery', 'window', 'document', 'confluence/api/constants', 'confluence-editor/editor/page-editor-message', 'confluence-editor/editor/page-editor-quit-dialog', 'confluence/analytics-support', 'confluence/legacy-editor-global-AVOID-IF-POSSIBLE', 'confluence/meta' ], function ( AJS, Templates, _, $, window, document, CONSTANTS, Message, QuitDialog, Analytics, Editor, Meta ) { "use strict"; var cleanupFunctions = []; // used to identify the source of and the action triggering an entity.stop or start var PUBLISH = 'confluence.editor.publish'; function isNewPage() { return Meta.getBoolean("new-page"); } function enableBar() { Editor.UI.toggleSavebarBusy(false); } function displayGenericSaveMessage(error) { var errorMessage = "There was an error processing the request."; Message.closeMessages(["generic-error"]); Message.handleMessage("generic-error", { type: "error", message: errorMessage }); AJS.logError("Generic error: " + JSON.stringify(error)); } function displayNoAuthorizedSaveMessage(triggerInvalidXsrfToken) { var errorMessage = "Your session has expired. You\'ll need to log in again or switch to another account to keep working.\n \u003cdiv\u003e\u003ca href=\"/login?application=confluence\"\u003eLog in or switch to another account\u003c/a\u003e\u003c/div\u003e"; Message.closeMessages(["noauthorized"]); Message.handleMessage("noauthorized", { title: "Can\'t connect to the server", type: "error", message: errorMessage }); if (triggerInvalidXsrfToken) { AJS.trigger("rte.safe-save.invalid-xsrf-token"); } } function displayServerOfflineSaveMessage() { var errorMessage = "Unable to communicate with server. Saving is not possible at the moment."; Message.closeMessages(["server-offline"]); Message.handleMessage("server-offline", { type: "error", message: errorMessage }); } var genericSaveErrorMessageTimeout; function cancelGenericSaveErrorMessage() { if (genericSaveErrorMessageTimeout) { clearTimeout(genericSaveErrorMessageTimeout); } } function displayGenericSaveErrorMessage() { cancelGenericSaveErrorMessage(); genericSaveErrorMessageTimeout = setTimeout(function(){ displayServerOfflineSaveMessage(); }, 500); } function stopHeartbeatAlwaysCallback(responseJSON) { var newLocation = responseJSON._links.webui; if (!newLocation) { enableBar(); return; } if (newLocation.indexOf('/') !== 0) { //relative address to current path window.location = newLocation; } else { // full virtual path, needs to be joined to context path. window.location = CONSTANTS.CONTEXT_PATH + newLocation; } } var SafeSave = {}; SafeSave._internal = {}; // successful http response with no validation errors SafeSave._internal.onSuccessfulResponse = function (responseJSON) { // exposed for testing purposes var data = { dataType: 'json', contentId: Meta.get('content-id'), draftType: Meta.get('draft-type') }; $.ajax({ url: CONSTANTS.CONTEXT_PATH + '/api/v2/editor/heartbeat/stop', type: 'POST', data: JSON.stringify(data), dataType: 'json', headers: { Accept: 'application/json', "Content-Type": 'application/json' }, success: function () { AJS.log('Stop heartbeat activity on', data.draftType, 'id', data.contentId); } }).fail(function (xhr, status, err) { AJS.logError('Server error on stop heartbeat activity request:'); AJS.log(err); }).always( stopHeartbeatAlwaysCallback.bind(this, responseJSON) ); }; SafeSave.initialize = function () { if ($("#editpageform").length === 0 && $("#createpageform").length === 0) { return; } var allowedSaveErrorMessage = { duplicatedTitle: "A page with this title already exists", titleTooLong: "Title cannot be longer than 255 characters." }; var draftData = { pageId: Meta.get("page-id"), type: Meta.get("draft-type"), spaceKey: Meta.get('space-key') }; QuitDialog.init({saveHandler: onSave}); Editor.overrideSave(QuitDialog.process); var retries = 0; var MAX_RETRIES = 3; var RETRY_DELAY = 1000; var retrySaveTimeouts = []; function clearAllTimeouts() { while (retrySaveTimeouts.length) { clearTimeout(retrySaveTimeouts.shift()); } } function onSave(e) { AJS.trigger('synchrony.stop', {id: PUBLISH}); if (e) { e.preventDefault(); } function computeParentData() { var parentData = {}; parentData.id = Meta.get("parent-page-id") || "0"; parentData.type = Meta.get("content-type"); var parentPageIdInput = Meta.get('parent-page-id'); //If space has been changed if (!parentPageIdInput || jsonData.space.key !== Meta.get("space-key")) { parentData.id = "0"; } //If parent page id is not empty and has been changed if (parentPageIdInput && parentData.id !== parentPageIdInput) { parentData.id = ($("#parentPageString").val() === Meta.get("from-page-title")) ? parentData.id : parentPageIdInput; } return parentData; } // Run any cleanup functions that have been registered. function cleanupContent(content) { cleanupFunctions.forEach(function (cleanupFunc) { content = cleanupFunc(content); }); return content; } function retrySave(xhr) { if (retries < MAX_RETRIES) { retries++; //remember timeout ID so we can clear all timeout later retrySaveTimeouts.push(setTimeout(function () { onSave(); }, RETRY_DELAY)); } else { AJS.trigger('analyticsEvent', {name: 'editor.save.error.conflict'}); retries = 0; Message.handleMessage("page-conflict", { title: "Can\'t sync with the server.", type: "error", message: AJS.format("Refresh the page to try to re-establish the connection.", jsonData.space.key) }); afterFailedSaveAttempt(xhr); } } function afterFailedSaveAttempt(xhr) { AJS.trigger("rte.safe-save.error", {status: xhr.status}); } var $contentTitle = $("#content-title"); if ($contentTitle.hasClass("placeholded") || $contentTitle.val().trim() === "") { AJS.trigger("rte.safe-save.error"); AJS.trigger('synchrony.start', {id: PUBLISH}); Message.closeMessages(["title-too-long", "duplicate-title"]); Message.handleMessage("empty-title", { title: "This content needs a name", type: "error", message: "Add a page title before hitting publish." }); enableBar(); var deferred = $.Deferred(); deferred.fail(); return deferred.promise(); } Editor.Drafts.unBindUnloadMessage(); var jsonData = {}; var draftId = Meta.get('draft-id'); var contentId = Meta.get('content-id'); var url = CONSTANTS.CONTEXT_PATH + "/rest/api/content"; var sourceTemplateId = $("#sourceTemplateId").val(); jsonData.status = "current"; jsonData.title = $contentTitle.val(); jsonData.space = {key: Meta.get('space-key')}; jsonData.body = { editor: { value: cleanupContent(AJS.Rte.getEditor().getContent()), representation: "editor" } }; var extensions = { "update-trigger": "EDIT_PAGE" }; if (sourceTemplateId) { extensions.sourceTemplateId = sourceTemplateId; } jsonData.extensions = extensions; var datePicker = Editor.UI.postingDatePicker; // datePicker.getDate() could be undefined in case the date isn't changed var publishedDate = datePicker && datePicker.getDate(); if (draftData.type === "blogpost" && publishedDate && publishedDate.toISOString) { jsonData.history = { "createdDate": publishedDate.toISOString() // supported by all major browsers and IE9 }; } if (isNewPage() && Meta.get("is-blueprint-page")) { url = url + "/blueprint/instance"; } url = url + "/" + contentId; //`draft-id` is content id with `draft` status for shared drafts if (isNewPage()) { jsonData.id = draftId; jsonData.body.editor.content = {id: draftId}; } else { jsonData.id = Meta.get('page-id'); jsonData.body.editor.content = {id: Meta.get('page-id')}; } //if there is no draft at the time save button is clicked, we update content directly if (draftId === "0") { url = url + "?status=current"; } else { url = url + "?status=draft"; } var versionNumber = Meta.getNumber("page-version") || 0; jsonData.type = Meta.get('content-type'); jsonData.version = { number: versionNumber + 1, message: $("#versionComment").val(), minorEdit: ($("#notifyWatchers").length && !$("#notifyWatchers").is(":checked")) ? true : false, syncRev: $("#syncRev").val() }; var parentData = computeParentData(); if (parentData.id !== "0") { jsonData.ancestors = [parentData]; } return $.ajax({ type: "PUT", url: url, contentType: "application/json; charset=utf-8", dataType: "json", data: JSON.stringify(jsonData), success: function (data) { cancelGenericSaveErrorMessage(); AJS.trigger("rte.safe-save.success", data); SafeSave._internal.onSuccessfulResponse(data); }, error: function (xhr) { AJS.trigger("rte.safe-save.error", { status: xhr.status }); var retrying = false; enableBar(); switch (xhr.status) { case 400: Message.closeMessages(["empty-title", "duplicate-title", "title-too-long"]); if (xhr.responseText.indexOf(allowedSaveErrorMessage.duplicatedTitle) >= 0) { Message.handleMessage("duplicate-title", { type: "error", message: AJS.format("A page with the title \'\'{0}\'\' already exists in this space. Enter a different title for your page.", AJS.escapeHtml($("#content-title").val())) }); } else if (xhr.responseText.indexOf(allowedSaveErrorMessage.titleTooLong) >= 0) { Message.handleMessage("title-too-long", { type: "error", message: "Title cannot be longer than 255 characters." }); } else { displayGenericSaveMessage(xhr); } break; case 403: displayNoAuthorizedSaveMessage(true); break; case 404: Message.closeMessages(["page-not-accessible", "noauthorized"]); Message.handleMessage("page-not-accessible", { title: "This content cannot be accessed.", type: "error", message: AJS.format("Your session may have expired, you can attempt to \u003ca href=\"/login?application=confluence\"\u003elog in\u003c/a\u003e.", jsonData.space.key) }); break; case 409: // TODO SHARED-DRAFTS CONFDEV-43949 No spinner or indicator when reliable save finds a conflict and is doing the retries retrying = true; retrySave(xhr); break; case 410: Message.closeMessages(["page-deleted"]); Message.handleMessage("page-deleted", { title: "This content has been deleted", type: "error", message: AJS.format("Copy your content, then add it to a new page or \u003cdiv\u003e\u003ca href=\"viewtrash.action?key={0}\" target=\"_blank\"\u003erestore this page from the trash\u003c/a\u003e and try again.\u003c/div\u003e", jsonData.space.key) }); break; case 413: Message.closeMessages(["page-too-big"]); Message.handleMessage("page-too-big", { type: "error", message: "This page is too big to save. You could split it into multiple pages, then use the \u003ca href=\"https://confluence.atlassian.com/conf51/include-page-macro-336169384.html\" target=\"_blank\"\u003einclude page macro\u003c/a\u003e to display the content." }); break; case 0: case 500: case 503: //CONFDEV-43951 clearAllTimeouts(); if (xhr.status === 500) { // CFE-1775 displayGenericSaveErrorMessage(); } else { displayServerOfflineSaveMessage(); } break; default: displayGenericSaveMessage(xhr); break; } if (!retrying) { afterFailedSaveAttempt(xhr); } AJS.trigger('synchrony.start', {id: PUBLISH}); } }); } // ----------------------------- // Heartbeat binding. Heartbeating takes care of enabling/disabling the save bar. // ----------------------------- AJS.bind("rte.heartbeat-error", function (sender, err) { switch (err.status) { case 401: case 403: if (!Message.isDisplayed(["page-not-accessible"])) { displayNoAuthorizedSaveMessage(false); } break; case 0: case 404: case 500: case 503: if (!Editor.metadataSyncRequired()) { displayServerOfflineSaveMessage(); } break; default : AJS.logError("Heartbeat action error: " + JSON.stringify(err)); } }); AJS.bind("rte.heartbeat", function (sender) { var isDisplayed = false; _.each(Message.displayedErrors(), function (error) { if (_.contains(["noauthorized", "server-offline", "page-not-accessible"], error)) { isDisplayed = true; Message.closeMessages([error]); } }); if (isDisplayed) { Message.handleMessage("reconnect", { type: "info", title: "Successfully reconnected", message: "We\'re back in business. You\'re free to save your page again.", close: "auto" }); } }); }; SafeSave.registerCleanupFunction = function (cleanupFunction) { cleanupFunctions.push(cleanupFunction); }; return SafeSave; }); require('confluence/module-exporter').safeRequire('confluence-editor-reliable-save/reliable-save', function (SafeSave) { var AJS = require('ajs'); var Meta = require('confluence/meta'); var $ = require('jquery'); //CONFDEV-38205 var isCreatingPageFromRoadMapBar = window.document.referrer.indexOf('createDialog=true&flashId') > 0; if (!AJS.DarkFeatures.isEnabled('editor.ajax.save') || Meta.get('remote-user') === '' || isCreatingPageFromRoadMapBar) { // DO NOT BIND MODULE OR EXPOSE GLOBALS } else { AJS.bind("rte.init.ui", function () { SafeSave.initialize(); Confluence.Editor = Confluence.Editor || {}; Confluence.Editor.SafeSave = Confluence.Editor.SafeSave || {}; Confluence.Editor.SafeSave._internal = SafeSave._internal || {}; var fixSpans = function (content) { // CONFCLOUD-58745: There was an issue with Synchrony which produced repeated nested span tag, which is believed // to be fixed. This function is called to fix the messed up content that has already been stored in the Confluence DB. // The RegEx should match an open span tag, or a closed span tag, that exactly repeats itself at least 12 times. // Then replace all repeated tags with just one. var duplicatedSpans = /(<[/]?span[^>]*>)\1\1\1\1\1\1\1\1\1\1\1(\1)*/; return $("