diff --git a/Classes/Backend/Controller/PersistenceController.php b/Classes/Backend/Controller/PersistenceController.php new file mode 100644 index 0000000..66b2d2e --- /dev/null +++ b/Classes/Backend/Controller/PersistenceController.php @@ -0,0 +1,76 @@ +getJsonPayload($request); + + $data = $input['data'] ?? []; + unset($input['data']); + $cmdArray = $input['cmdArray'] ?? []; + unset($input['cmdArray']); + if (!is_array($data)) { + throw new RuntimeException('Data must be an array of table names to rows', 5781185589); + } + + if (!is_array($cmdArray)) { + throw new RuntimeException('Command array must be a list of DataHandler commands', 4576273831); + } + + if ($input !== []) { + throw new RuntimeException('Unknown data operations: ' . implode(', ', array_keys($input)) . ' only data and cmdArray are allowed', 8110225095); + } + + $GLOBALS['TYPO3_REQUEST'] = $request; + $errorLog = $this->dataHandlerService->run($data, []); + + foreach ($cmdArray as $cmd) { + $errorLog = [...$errorLog, ...$this->dataHandlerService->run([], $cmd)]; + } + + if ($errorLog) { + return new JsonResponse(['success' => false, 'errorLog' => $errorLog], 500); + } + + return new JsonResponse(['success' => true]); + } + + /** + * @return array + */ + private function getJsonPayload(ServerRequestInterface $request): array + { + $payload = $request->getParsedBody(); + if (!is_array($payload)) { + $payload = json_decode((string)$request->getBody(), true, 512, JSON_THROW_ON_ERROR); + } + + if (!is_array($payload)) { + throw new RuntimeException('Save payload must be a JSON object', 2634277014); + } + + return $payload; + } +} diff --git a/Classes/Middleware/PersistenceMiddleware.php b/Classes/Middleware/EditModeMiddleware.php similarity index 53% rename from Classes/Middleware/PersistenceMiddleware.php rename to Classes/Middleware/EditModeMiddleware.php index 733cf49..5bb7daf 100644 --- a/Classes/Middleware/PersistenceMiddleware.php +++ b/Classes/Middleware/EditModeMiddleware.php @@ -8,7 +8,6 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use RuntimeException; use TYPO3\CMS\Backend\Middleware\JavaScriptLabelImportMapEntryResolver; use TYPO3\CMS\Backend\Routing\Router; use TYPO3\CMS\Backend\Routing\UriBuilder; @@ -17,33 +16,23 @@ use TYPO3\CMS\Core\Context\VisibilityAspect; use TYPO3\CMS\Core\Error\Http\UnauthorizedException; use TYPO3\CMS\Core\EventDispatcher\ListenerProvider; -use TYPO3\CMS\Core\FormProtection\FormProtectionFactory; use TYPO3\CMS\Core\Http\HtmlResponse; use TYPO3\CMS\Core\Http\ImmediateResponseException; -use TYPO3\CMS\Core\Http\JsonResponse; use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Page\Event\ResolveVirtualJavaScriptImportEvent; use TYPO3\CMS\Core\Page\PageRenderer; -use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\View\ViewFactoryData; use TYPO3\CMS\Core\View\ViewFactoryInterface; -use TYPO3\CMS\Frontend\Page\PageInformation; -use TYPO3\CMS\VisualEditor\Service\DataHandlerService; -use function array_keys; -use function implode; -use function json_decode; use function substr; -readonly class PersistenceMiddleware implements MiddlewareInterface +readonly class EditModeMiddleware implements MiddlewareInterface { public function __construct( private Context $context, - private DataHandlerService $dataHandlerService, private UriBuilder $uriBuilder, private ViewFactoryInterface $viewFactory, - private FormProtectionFactory $formProtectionFactory, private Typo3Version $typo3Version, private ListenerProvider $listenerProvider, private PageRenderer $pageRenderer, @@ -52,54 +41,19 @@ public function __construct( public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - return match ($this->whatToDo($request)) { - MiddlewareAction::Edit => $this->handleEdit($request, $handler), - MiddlewareAction::Save => $this->saveStuff($request), - MiddlewareAction::None => $handler->handle($request), - }; - } - - private function saveStuff(ServerRequestInterface $request): ResponseInterface - { - $token = $request->getHeaderLine('X-Request-Token'); - if (!$token || !$this->formProtectionFactory->createForType('backend')->validateToken($token, 'visual_editor', 'save')) { - throw new UnauthorizedException('Invalid or missing request token', 8148623595); - } - - $input = $request->getParsedBody() ?? - json_decode($request->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); - - $data = $input['data'] ?? []; - unset($input['data']); - $cmdArray = $input['cmdArray'] ?? []; - unset($input['cmdArray']); - - if (!empty($input)) { - throw new RuntimeException('Unknown data operations: ' . implode(', ', array_keys($input)) . ' only data and cmdArray are allowed', 8110225095); + if ($this->shouldInitEditMode($request)) { + return $this->handleEdit($request, $handler); } - // Required by DefaultSanitizerBuilder when processing RTE fields via DataHandler; - // this middleware short-circuits before the FE RequestHandler which normally sets this global. - $GLOBALS['TYPO3_REQUEST'] = $request; - $errorLog = $this->dataHandlerService->run($data, []); - - foreach ($cmdArray as $cmd) { - $errorLog = [...$errorLog, ...$this->dataHandlerService->run([], $cmd)]; - } - - if ($errorLog) { - return new JsonResponse(['success' => false, 'errorLog' => $errorLog], 500); - } - - return new JsonResponse(['success' => true]); + return $handler->handle($request); } - private function whatToDo(ServerRequestInterface $request): MiddlewareAction + private function shouldInitEditMode(ServerRequestInterface $request): bool { // parameter editMode must be set $params = $request->getQueryParams(); if (!isset($params['editMode'])) { - return MiddlewareAction::None; + return false; } // backend user required @@ -123,42 +77,7 @@ private function whatToDo(ServerRequestInterface $request): MiddlewareAction throw new UnauthorizedException('No $GLOBALS[\'BE_USER\'] available', 8725323237); } - // only do something on POST requests - if ($request->getMethod() !== 'POST') { - return MiddlewareAction::Edit; - } - - // only allow application/json content type - if ($request->getHeaderLine('Content-Type') !== 'application/json') { - throw new UnauthorizedException('Content-Type must be application/json to save stuff with visual_editor', 5015404100); - } - - if ($user->isAdmin()) { - return MiddlewareAction::Save; - } - - // check permissions of user on page - $pageInformation = $this->getPageInformation($request); - - if (!$beUser->isInWebMount($pageInformation->getId())) { - throw new UnauthorizedException('No permission to access this page', 1610177162); - } - - if (!$beUser->doesUserHaveAccess($pageInformation->getPageRecord(), Permission::CONTENT_EDIT)) { - throw new UnauthorizedException('No permission to edit content on this page', 7668402611); - } - - return MiddlewareAction::Save; - } - - private function getPageInformation(ServerRequestInterface $request): PageInformation - { - $frontendPageInformation = $request->getAttribute('frontend.page.information'); - if (!$frontendPageInformation instanceof PageInformation) { - throw new RuntimeException('No frontend page information available', 7005099635); - } - - return $frontendPageInformation; + return true; } private function handleEdit(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface diff --git a/Classes/Middleware/MiddlewareAction.php b/Classes/Middleware/MiddlewareAction.php deleted file mode 100644 index 3661922..0000000 --- a/Classes/Middleware/MiddlewareAction.php +++ /dev/null @@ -1,12 +0,0 @@ - [ @@ -11,4 +12,10 @@ 'methods' => ['POST'], 'inheritAccessFromModule' => 'web_edit', ], + 'visual_editor_save' => [ + 'path' => '/visual-editor/save', + 'target' => PersistenceController::class . '::saveAction', + 'methods' => ['POST'], + 'inheritAccessFromModule' => 'web_edit', + ], ]; diff --git a/Configuration/RequestMiddlewares.php b/Configuration/RequestMiddlewares.php index 1cce003..eef6a17 100644 --- a/Configuration/RequestMiddlewares.php +++ b/Configuration/RequestMiddlewares.php @@ -3,12 +3,12 @@ declare(strict_types=1); use TYPO3\CMS\VisualEditor\Middleware\DisableCacheInEditModeMiddleware; -use TYPO3\CMS\VisualEditor\Middleware\PersistenceMiddleware; +use TYPO3\CMS\VisualEditor\Middleware\EditModeMiddleware; return [ 'frontend' => [ 'typo3/cms-visual-editor/persistence-middleware' => [ - 'target' => PersistenceMiddleware::class, + 'target' => EditModeMiddleware::class, 'after' => [ 'typo3/cms-frontend/prepare-tsfe-rendering', 'typo3/cms-frontend/tsfe', // TODO typo3/cms-frontend/tsfe can be dropped if TYPO3 14 is lowest supported version diff --git a/Resources/Public/JavaScript/Backend/components/ve-auto-save-toggle.js b/Resources/Public/JavaScript/Backend/components/ve-auto-save-toggle.js index 6ee02bf..8dda419 100644 --- a/Resources/Public/JavaScript/Backend/components/ve-auto-save-toggle.js +++ b/Resources/Public/JavaScript/Backend/components/ve-auto-save-toggle.js @@ -1,6 +1,7 @@ import {css, html, LitElement} from 'lit'; import {onMessageDebounced, sendMessage} from '@typo3/visual-editor/Shared/iframe-messaging'; import {autoSaveActive} from '@typo3/visual-editor/Shared/local-stores'; +import {VeBackendSaveButton} from '@typo3/visual-editor/Backend/components/ve-backend-save-button'; /** @@ -76,7 +77,7 @@ export class VeAutoSaveToggle extends LitElement { this.count = count; this.invalidCount = invalidCount; if (this.active && this.count > 0 && this.invalidCount === 0) { - sendMessage('doSave'); + this.triggerDoSave(); } } @@ -91,9 +92,18 @@ export class VeAutoSaveToggle extends LitElement { autoSaveActive.set(this.active); if (this.active && this.count > 0 && this.invalidCount === 0) { - sendMessage('doSave'); + this.triggerDoSave(); } } + + triggerDoSave() { + const element = document.querySelector('ve-backend-save-button'); + if (!(element instanceof VeBackendSaveButton)) { + throw new Error('ve-backend-save-button is missing, could not autosave'); + } + element.doSave(); + } + #onKeydown(e) { if (this.hasAttribute('disabled') || (e.key !== 'Enter' && e.key !== ' ')) { return; diff --git a/Resources/Public/JavaScript/Backend/components/ve-backend-save-button.js b/Resources/Public/JavaScript/Backend/components/ve-backend-save-button.js index 4987542..e4385f9 100644 --- a/Resources/Public/JavaScript/Backend/components/ve-backend-save-button.js +++ b/Resources/Public/JavaScript/Backend/components/ve-backend-save-button.js @@ -1,7 +1,12 @@ import {css, html, LitElement} from 'lit'; import {lll} from "@typo3/core/lit-helper.js"; import {onMessage, sendMessage} from '@typo3/visual-editor/Shared/iframe-messaging'; +import {useDataHandler} from '@typo3/visual-editor/Backend/use-data-handler'; +import {reloadAllChildFrames} from '@typo3/visual-editor/Backend/reload-all-child-frames'; +/** + * @type {{data: Object, cmdArray: Object[], invalidFields: Object, count: number, invalidCount: number}} + */ let lastInfo = null; /** @@ -36,8 +41,7 @@ export class VeBackendSaveButton extends LitElement { this.onClick = this.#onClick.bind(this); this.onKeydown = this.#onKeydown.bind(this); this.disposeUpdateEditorStateListener = null; - this.disposeOnSaveListener = null; - this.disposeSaveEndedListener = null; + this.disposeDoSaveListener = null; if (lastInfo) { this.onUpdateEditorState(lastInfo); } @@ -49,11 +53,8 @@ export class VeBackendSaveButton extends LitElement { if (!this.disposeUpdateEditorStateListener) { this.disposeUpdateEditorStateListener = onMessage('updateEditorState', this.onUpdateEditorState.bind(this)); } - if (!this.disposeOnSaveListener) { - this.disposeOnSaveListener = onMessage('onSave', this.onSaveMessage.bind(this)); - } - if (!this.disposeSaveEndedListener) { - this.disposeSaveEndedListener = onMessage('saveEnded', this.onSaveEndedMessage.bind(this)); + if (!this.disposeDoSaveListener) { + this.disposeDoSaveListener = onMessage('doSave', this.doSave.bind(this)); } this.addEventListener('click', this.onClick); @@ -63,10 +64,8 @@ export class VeBackendSaveButton extends LitElement { disconnectedCallback() { this.disposeUpdateEditorStateListener?.(); this.disposeUpdateEditorStateListener = null; - this.disposeOnSaveListener?.(); - this.disposeOnSaveListener = null; - this.disposeSaveEndedListener?.(); - this.disposeSaveEndedListener = null; + this.disposeDoSaveListener?.(); + this.disposeDoSaveListener = null; this.removeEventListener('click', this.onClick); this.removeEventListener('keydown', this.onKeydown); @@ -102,12 +101,42 @@ export class VeBackendSaveButton extends LitElement { this.invalidCount = info.invalidCount; } - onSaveMessage() { - this.saving = true; - } + async doSave() { + if (this.isInteractionDisabled) { + return; + } - onSaveEndedMessage({updatePageTree}) { - this.saving = false; + const count = lastInfo.count; + const invalidCount = lastInfo.invalidCount; + if (invalidCount > 0) { + sendMessage('focusFirstInvalidField'); + return; + } + + if (this.saving) { + // already saving currently. + return; + } + + if (count === 0) { + // nothing to save. + return; + } + + const updatePageTree = 'pages' in lastInfo.data; + + try { + this.saving = true; + const saveOk = await useDataHandler(lastInfo.data, lastInfo.cmdArray); + requestAnimationFrame(() => { + sendMessage('saveEnded'); + }) + if (!saveOk) { + reloadAllChildFrames(); + } + } finally { + this.saving = false; + } if (updatePageTree) { top.document.dispatchEvent(new CustomEvent('typo3:pagetree:refresh')); @@ -116,10 +145,7 @@ export class VeBackendSaveButton extends LitElement { #onClick(e) { e.preventDefault(); - if (this.isInteractionDisabled) { - return; - } - sendMessage('doSave'); + this.doSave(); } #onKeydown(e) { @@ -127,7 +153,7 @@ export class VeBackendSaveButton extends LitElement { return; } e.preventDefault(); - sendMessage('doSave'); + this.doSave(); } get hasChanges() { diff --git a/Resources/Public/JavaScript/Backend/index.js b/Resources/Public/JavaScript/Backend/index.js index f104dcb..4deb9d9 100644 --- a/Resources/Public/JavaScript/Backend/index.js +++ b/Resources/Public/JavaScript/Backend/index.js @@ -7,16 +7,10 @@ import '@typo3/visual-editor/Backend/components/ve-show-empty-toggle'; import '@typo3/visual-editor/Backend/components/ve-show-hidden-toggle'; import {pageChanged} from '@typo3/visual-editor/Backend/page-changed'; import {initializePageTreeSaveState} from '@typo3/visual-editor/Backend/initialize-page-tree-save-state'; +import {reloadAllChildFrames} from '@typo3/visual-editor/Backend/reload-all-child-frames'; initializePageTreeSaveState(); -function reloadAllChildFrames() { - const iframes = document.querySelectorAll('iframe'); - iframes.forEach((iframe) => { - iframe.contentWindow.location.reload(); - }); -} - /** * @param src {string} * @param title {string} diff --git a/Resources/Public/JavaScript/Backend/reload-all-child-frames.js b/Resources/Public/JavaScript/Backend/reload-all-child-frames.js new file mode 100644 index 0000000..4b1143e --- /dev/null +++ b/Resources/Public/JavaScript/Backend/reload-all-child-frames.js @@ -0,0 +1,6 @@ +export function reloadAllChildFrames() { + const iframes = document.querySelectorAll('iframe'); + iframes.forEach((iframe) => { + iframe.contentWindow.location.reload(); + }); +} diff --git a/Resources/Public/JavaScript/Frontend/use-data-handler.js b/Resources/Public/JavaScript/Backend/use-data-handler.js similarity index 83% rename from Resources/Public/JavaScript/Frontend/use-data-handler.js rename to Resources/Public/JavaScript/Backend/use-data-handler.js index 278859e..0e8160a 100644 --- a/Resources/Public/JavaScript/Frontend/use-data-handler.js +++ b/Resources/Public/JavaScript/Backend/use-data-handler.js @@ -6,14 +6,13 @@ import {lll} from "@typo3/core/lit-helper.js"; * @typedef {Record>>} Cmd * @param {Data} data * @param {Cmd[]} cmdArray - * @returns {Promise} returns false if something broke + * @returns {Promise} */ export async function useDataHandler(data = {}, cmdArray = []) { - const response = await fetch(window.location.href, { + const response = await fetch(TYPO3.settings.ajaxUrls.visual_editor_save, { method: 'POST', headers: { 'Content-Type': 'application/json', - 'X-Request-Token': window.veInfo.token, }, body: JSON.stringify({data, cmdArray}, null, 2), }); @@ -24,7 +23,6 @@ export async function useDataHandler(data = {}, cmdArray = []) { let body = await response.text(); - // if response has json parse it and check if errorLog is included: if (response.headers.get('Content-Type')?.includes('application/json')) { const data = JSON.parse(body); if (data.errorLog) { diff --git a/Resources/Public/JavaScript/Frontend/components/ve-content-element.js b/Resources/Public/JavaScript/Frontend/components/ve-content-element.js index 770e863..91f2b33 100644 --- a/Resources/Public/JavaScript/Frontend/components/ve-content-element.js +++ b/Resources/Public/JavaScript/Frontend/components/ve-content-element.js @@ -374,6 +374,7 @@ export class VeContentElement extends LitElement { .action-bar.hidden { display: flex; opacity: 0.5; + pointer-events: initial; } .action-bar-headline { diff --git a/Resources/Public/JavaScript/Frontend/components/ve-drop-zone.js b/Resources/Public/JavaScript/Frontend/components/ve-drop-zone.js index dbb8967..4bb2c4f 100644 --- a/Resources/Public/JavaScript/Frontend/components/ve-drop-zone.js +++ b/Resources/Public/JavaScript/Frontend/components/ve-drop-zone.js @@ -2,16 +2,12 @@ import {css, html, LitElement} from 'lit'; import {lll} from "@typo3/core/lit-helper.js"; import {classMap} from 'lit/directives/class-map.js'; import {styleMap} from 'lit/directives/style-map.js'; -import {sendMessage} from '@typo3/visual-editor/Shared/iframe-messaging'; -import {useDataHandler} from '@typo3/visual-editor/Frontend/use-data-handler'; +import {onMessage, sendMessage} from '@typo3/visual-editor/Shared/iframe-messaging'; import {dragInProgressStore} from '@typo3/visual-editor/Frontend/stores/drag-store'; import {flipInsertBefore} from '@typo3/visual-editor/Frontend/flip-insert-before'; import {dataHandlerStore} from '@typo3/visual-editor/Frontend/stores/data-handler-store'; import {autoNoOverlap, calculateAllDebounced} from '@typo3/visual-editor/Frontend/auto-no-overlap'; -import { - DROP_ZONE_LABEL_FIT_DEFAULTS, - fitDropZoneLabel -} from '@typo3/visual-editor/Frontend/components/ve-drop-zone/label-fitting'; +import {DROP_ZONE_LABEL_FIT_DEFAULTS, fitDropZoneLabel} from '@typo3/visual-editor/Frontend/components/ve-drop-zone/label-fitting'; const DROP_ZONE_LABEL_EDGE_LEEWAY = 5; @@ -201,7 +197,7 @@ export class VeDropZone extends LitElement { Number.isInteger(this.tx_container_parent) ? {tx_container_parent: this.tx_container_parent} : {} - ) + ), }, }; @@ -216,9 +212,12 @@ export class VeDropZone extends LitElement { } dataHandlerStore.addCmd(data.table, data.uid, 'copy', actionData); - await useDataHandler(dataHandlerStore.data, dataHandlerStore.cmdArray); - dataHandlerStore.markSaved(); - sendMessage('reloadFrames'); + + sendMessage('doSave'); + const unsubscribe = onMessage('saveEnded', () => { + unsubscribe(); + sendMessage('reloadFrames'); + }); return; } @@ -459,7 +458,7 @@ export class VeDropZone extends LitElement { if (this.target < 0) { afterParts.push( {type: 'label', text: lll('frontend.after')}, - {type: 'value', text: this.getComponentName(this.target * -1)} + {type: 'value', text: this.getComponentName(this.target * -1)}, ); } if (this.tx_container_parent || this.colPos > 99) { @@ -467,7 +466,7 @@ export class VeDropZone extends LitElement { const uidOfParent = this.tx_container_parent || parseInt(this.colPos / 100); containerParts.push( {type: 'label', text: lll('frontend.in')}, - {type: 'value', text: this.getComponentName(uidOfParent)} + {type: 'value', text: this.getComponentName(uidOfParent)}, ); } diff --git a/Resources/Public/JavaScript/Frontend/initialize-save-handling.js b/Resources/Public/JavaScript/Frontend/initialize-save-handling.js index ac19641..c082ce8 100644 --- a/Resources/Public/JavaScript/Frontend/initialize-save-handling.js +++ b/Resources/Public/JavaScript/Frontend/initialize-save-handling.js @@ -1,12 +1,12 @@ import {onMessage, sendMessage} from '@typo3/visual-editor/Shared/iframe-messaging'; -import {useDataHandler} from '@typo3/visual-editor/Frontend/use-data-handler'; import {dataHandlerStore} from '@typo3/visual-editor/Frontend/stores/data-handler-store'; import {InterceptUserActionsGuard} from '@typo3/visual-editor/Frontend/intercept-user-actions-guard'; -let saving = false; - export function syncEditorState() { sendMessage('updateEditorState', { + data: dataHandlerStore.data, + cmdArray: dataHandlerStore.cmdArray, + invalidFields: dataHandlerStore.invalidFields, count: dataHandlerStore.changesCount, invalidCount: dataHandlerStore.invalidCount, }); @@ -16,35 +16,6 @@ export function focusFirstInvalidField() { document.querySelector('ve-editable-text[invalid]')?.focusEditable?.(); } -export async function trySave() { - const count = dataHandlerStore.changesCount; - const invalidCount = dataHandlerStore.invalidCount; - if (invalidCount > 0) { - syncEditorState(); - focusFirstInvalidField(); - return; - } - - if (saving || count === 0) { - return; - } - - saving = true; - sendMessage('onSave'); - - try { - const updatePageTree = dataHandlerStore.hasChangesIn('pages'); - const saveOk = await useDataHandler(dataHandlerStore.data, dataHandlerStore.cmdArray); - dataHandlerStore.markSaved(); - sendMessage('saveEnded', {updatePageTree}); - if (!saveOk) { - window.location.reload(); - } - } finally { - saving = false; - } -} - export function initializeSaveHandling() { syncEditorState(); dataHandlerStore.addEventListener('change', syncEditorState); @@ -54,11 +25,18 @@ export function initializeSaveHandling() { } event.preventDefault(); - trySave(); - }); - onMessage('doSave', () => { - trySave(); + syncEditorState(); + sendMessage('doSave'); }); new InterceptUserActionsGuard(dataHandlerStore); } + + +onMessage('focusFirstInvalidField', () => { + focusFirstInvalidField(); +}); + +onMessage('saveEnded', () => { + dataHandlerStore.markSaved(); +}); diff --git a/Resources/Public/JavaScript/Frontend/intercept-user-actions-guard.js b/Resources/Public/JavaScript/Frontend/intercept-user-actions-guard.js index de79638..b6c6b3c 100644 --- a/Resources/Public/JavaScript/Frontend/intercept-user-actions-guard.js +++ b/Resources/Public/JavaScript/Frontend/intercept-user-actions-guard.js @@ -1,7 +1,7 @@ import {lll} from "@typo3/core/lit-helper.js"; import Modal from '@typo3/backend/modal.js'; import Severity from '@typo3/backend/severity.js'; -import {trySave} from '@typo3/visual-editor/Frontend/initialize-save-handling'; +import {onMessage, sendMessage} from '@typo3/visual-editor/Shared/iframe-messaging'; export class InterceptUserActionsGuard { @@ -60,8 +60,11 @@ export class InterceptUserActionsGuard { btnClass: 'btn-primary', trigger: async (_e, modal) => { modal.hideModal(); - await trySave(); - resolve(); + sendMessage('doSave'); + const unsubscribe = onMessage('saveEnded', () => { + unsubscribe(); + resolve(); + }); }, }); } diff --git a/Resources/Public/JavaScript/Shared/iframe-messaging.js b/Resources/Public/JavaScript/Shared/iframe-messaging.js index d661960..0e47d92 100644 --- a/Resources/Public/JavaScript/Shared/iframe-messaging.js +++ b/Resources/Public/JavaScript/Shared/iframe-messaging.js @@ -3,10 +3,9 @@ * @property openModal {{ src: String, title: String, size: 'medium' | 'large' | 'full', type: 'iframe' | 'ajax' }} * @property closeModal {null} * @property reloadFrames {null} - * @property updateEditorState {{count: number, invalidCount: number}} + * @property updateEditorState {{data: Object, cmdArray: Object[], invalidFields: Object, count: number, invalidCount: number}} * @property doSave {null} - * @property onSave {null} - * @property saveEnded {{updatePageTree: boolean}} + * @property saveEnded {null} * @property pageChanged {{pageId: number, languageId: number}} * @property openInMiddleFrame {String} * @property localStoreChange {{key: String, value: any}} diff --git a/composer.json b/composer.json index b754324..9587fdf 100644 --- a/composer.json +++ b/composer.json @@ -31,8 +31,8 @@ "pluswerk/grumphp-config": "^10.2.7", "saschaegerer/phpstan-typo3": "^2.1.1 || ^3.0.1", "ssch/typo3-rector": "^3.14.1", - "typo3/cms-install": "^13.4.28 || ^14.3.2", - "typo3/cms-workspaces": "^13.4.28 || ^14.3.2", + "typo3/cms-install": "^13.4.28 || ^14.3.3", + "typo3/cms-workspaces": "^13.4.28 || ^14.3.3", "typo3/testing-framework": "^9.5.0", "typo3fluid/fluid": "^4.6.1 || ^5.3.1" }, diff --git a/phpstan-baseline-13.neon b/phpstan-baseline-13.neon index 05c9cd9..4b499bc 100644 --- a/phpstan-baseline-13.neon +++ b/phpstan-baseline-13.neon @@ -148,13 +148,13 @@ parameters: message: '#^Class TYPO3\\CMS\\Backend\\Middleware\\JavaScriptLabelImportMapEntryResolver not found\.$#' identifier: class.notFound count: 1 - path: Classes/Middleware/PersistenceMiddleware.php + path: Classes/Middleware/EditModeMiddleware.php - message: '#^Class TYPO3\\CMS\\Core\\Page\\Event\\ResolveVirtualJavaScriptImportEvent not found\.$#' identifier: class.notFound count: 1 - path: Classes/Middleware/PersistenceMiddleware.php + path: Classes/Middleware/EditModeMiddleware.php - message: '#^Call to an undefined method PhpParser\\Node\\Expr\|PhpParser\\Node\\Identifier\:\:toString\(\)\.$#'