Skip to content

Commit

Permalink
[BUGFIX] Allow creating bookmark after it was removed
Browse files Browse the repository at this point in the history
This commit addresses 2 bugs in BE bookmarks management:

- After adding and removing a page from bookmarks, it couldn't be
  re-added without reloading the page.

- If a page was bookmarked, the bookmark button wasn't marked as
  inactive/disabled during BE content rendering.

Given the current implementation challenges, like the inability to
reload the shortcuts dropdown view and difficulty in modifying
backend button classes, most of the handling was delegated to the FE.

- BE always returns the shortcut button with necessary data attributes
  for frontend dispatch actions. BE only sets (or omits) the
  `data-dispatch-disabled` and `disabled` attributes.

- For every bookmarked entry, the BE also provides its `route` and
  `args`.

- Upon FE bookmark removal, the BE response includes the `route` and
  `args`. The FE uses these to match the shortcut button's data
  attributes and, if they match, enables the button.

This change is effective for both the shortcuts dropdown and the
standalone bookmark button.

Resolves: #75689
Releases: main, 12.4
Change-Id: I1c67ed0c3faa5b1033e958a6346304a944e53d6d
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/81815
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
  • Loading branch information
Marcin Sągol authored and lolli42 committed Nov 21, 2023
1 parent b54062b commit 4b39f02
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 8 deletions.
50 changes: 46 additions & 4 deletions Build/Sources/TypeScript/backend/toolbar/shortcut-menu.ts
Expand Up @@ -34,6 +34,13 @@ enum Identifiers {
shortcutEditSelector = '.t3js-shortcut-edit',

shortcutFormSelector = '.t3js-shortcut-form',

createShortcutSelector = '[data-dispatch-action="TYPO3.ShortcutMenu.createShortcut"]',
}

interface BookmarkData {
route: string;
args: string;
}

/**
Expand Down Expand Up @@ -89,9 +96,13 @@ class ShortcutMenu {
Icons.getIcon('actions-system-shortcut-active', Icons.sizes.small).then((icon: string): void => {
shortcutButton.innerHTML = icon + (isDropdownItem ? ' ' + securityUtility.encodeHtml(TYPO3.lang['labels.alreadyBookmarked']) : '');
});
shortcutButton.classList.add(isDropdownItem ? 'disabled' : 'active');
// @todo using plain `disabled` HTML attr would have been better, since it disables events, mouse cursor, etc.
// (however, it might make things more complicated in Bootstrap's `button-variant` mixin)

if (isDropdownItem) {
shortcutButton.setAttribute('disabled', 'disabled');
} else {
shortcutButton.classList.add('active');
}

shortcutButton.dataset.dispatchDisabled = 'disabled';
shortcutButton.title = securityUtility.encodeHtml(TYPO3.lang['labels.alreadyBookmarked']);
}).catch((): void => {
Expand Down Expand Up @@ -154,11 +165,15 @@ class ShortcutMenu {
modal.addEventListener('confirm.button.ok', (): void => {
(new AjaxRequest(TYPO3.settings.ajaxUrls.shortcut_remove)).post({
shortcutId,
}).then((): void => {
}).then(async (response: AjaxResponse): Promise<void> => {
const data = await response.resolve();
// a reload is used in order to restore the original behaviour
// e.g. remove groups that are now empty because the last one in the group
// was removed
this.refreshMenu();
// if we remove bookmark entry for current page, we want to enable the bookmark link
// again in the dropdown menu
this.checkIfEnableBookmarkLink(data.data);
});
modal.hideModal();
});
Expand Down Expand Up @@ -207,6 +222,33 @@ class ShortcutMenu {
document.querySelector(Identifiers.containerSelector + ' typo3-backend-spinner').replaceWith(existingIcon);
});
}

private checkIfEnableBookmarkLink(bookmarkData: BookmarkData): void {
const shortcutButton: HTMLElement|null = top.list_frame?.document.querySelector(Identifiers.createShortcutSelector);
if (!shortcutButton) {
return;
}

const dispatchArgs = JSON.parse(shortcutButton.dataset.dispatchArgs.replace(/&quot;/g, '"'));
if (dispatchArgs[0] !== bookmarkData.route || dispatchArgs[1] !== bookmarkData.args) {
return;
}

const securityUtility = new SecurityUtility();
const isDropdownItem = shortcutButton.classList.contains('dropdown-item');

Icons.getIcon('actions-system-shortcut-new', Icons.sizes.small).then((icon: string): void => {
shortcutButton.innerHTML = icon + (isDropdownItem ? ' ' + securityUtility.encodeHtml(TYPO3.lang['labels.makeBookmark']) : '');
});
shortcutButton.title = securityUtility.encodeHtml(TYPO3.lang['labels.makeBookmark']);
delete shortcutButton.dataset.dispatchDisabled;

if (isDropdownItem) {
shortcutButton.removeAttribute('disabled');
} else {
shortcutButton.classList.remove('active');
}
}
}

if (!top.TYPO3.ShortcutMenu || typeof top.TYPO3.ShortcutMenu !== 'object') {
Expand Down
12 changes: 10 additions & 2 deletions typo3/sysext/backend/Classes/Controller/ShortcutController.php
Expand Up @@ -115,7 +115,15 @@ public function updateAction(ServerRequestInterface $request): ResponseInterface
*/
public function removeAction(ServerRequestInterface $request): ResponseInterface
{
$success = $this->shortcutRepository->removeShortcut((int)($request->getParsedBody()['shortcutId'] ?? 0));
return new JsonResponse(['success' => $success]);
$shortcut = $this->shortcutRepository->getShortcutById((int)($request->getParsedBody()['shortcutId'] ?? 0));
$success = $this->shortcutRepository->removeShortcut($shortcut['raw']['uid'] ?? 0);

return new JsonResponse([
'success' => $success,
'data' => [
'route' => $shortcut['route'] ?? null,
'args' => $shortcut['raw']['arguments'] ?? null,
],
]);
}
}
Expand Up @@ -224,13 +224,15 @@ public function render()
// Shortcut Button
$shortcutItem = GeneralUtility::makeInstance(DropDownItem::class);
$shortcutItem->setTag('button');
$attributes = $this->getDispatchActionAttrs($routeIdentifier, $encodedArguments, $confirmationText);
if (GeneralUtility::makeInstance(ShortcutRepository::class)->shortcutExists($routeIdentifier, $encodedArguments)) {
$shortcutItem->setLabel($alreadyBookmarkedText);
$shortcutItem->setIcon($iconFactory->getIcon('actions-system-shortcut-active', Icon::SIZE_SMALL));
$shortcutItem->setAttributes($attributes + ['data-dispatch-disabled' => 'disabled', 'disabled' => 'disabled']);
} else {
$shortcutItem->setLabel($confirmationText);
$shortcutItem->setIcon($iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL));
$shortcutItem->setAttributes($this->getDispatchActionAttrs($routeIdentifier, $encodedArguments, $confirmationText));
$shortcutItem->setAttributes($attributes);
}
$dropdownItems[] = $shortcutItem;

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4b39f02

Please sign in to comment.