Commit 24f43811 authored by Oliver Bartsch's avatar Oliver Bartsch Committed by Benni Mack
Browse files

[FEATURE] Simplify sharing of backend urls

Atfer a lot of preperation in #93048 and #93988,
it's finally possible to share URLs to TYPO3 backend
modules. Even special modules such as FormEngine.

To ease the use for editors, the ShortcutButton
is extended for a new option. If enabled, which
is the default behaviour, the shortcut button
in the module header is replaced. The new button
allows to open a dropdown with the additional
possibility to copy the URL of the current
page to the operating systems' clipboard. Next
to the already exisiting "Add shortcut" option.

Since those URLs should not contain the user
specific token, the UriBuilder features a new
constant "SHAREABLE_URL". If set as $referenceType
in one of the supporting methods, e.g. buildUriFromRoute(),
an absoulte URL without the token is returned.

To copy the URL to the operating systems' clipboard,
a new web component "CopyToClipboard" is introduced.
This component is added without any dependency to
the URL sharing functionality and can therefore be
freely used for other backend components as well.

For the new button, the font awesome "share-alt"
icon is registered in the IconRegistry.

Resolves: #93921
Releases: master
Change-Id: Id1dcfe1f2af764fbe000659ddb49a7369322d5b6
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/69338

Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Jochen's avatarJochen <rothjochen@gmail.com>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Jochen's avatarJochen <rothjochen@gmail.com>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 6c3a9737
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
import {html, TemplateResult, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators';
import Notification = require('TYPO3/CMS/Backend/Notification');
import {lll} from 'TYPO3/CMS/Core/lit-helper';
/**
* Module: TYPO3/CMS/Backend/CopyToClipboard
*
* This module can be used to copy a given text to
* the operating systems' clipboard.
*
* @example
* <typo3-copy-to-clipboard text="some text">
* <button>Copy to clipboard</button>
* </typo3-copy-to-clipboard>
*/
@customElement('typo3-copy-to-clipboard')
class CopyToClipboard extends LitElement {
@property({type: String}) text: string;
public constructor() {
super();
this.addEventListener('click', (e: Event): void => {
e.preventDefault();
this.copyToClipboard()
});
}
protected render(): TemplateResult {
return html`<slot></slot>`;
}
private copyToClipboard(): void {
if (typeof this.text !== 'string' || !this.text.length) {
console.warn('No text for copy to clipboard given.')
Notification.error(lll('copyToClipboard.error'));
return;
}
if (navigator.clipboard) {
navigator.clipboard.writeText(this.text).then((): void => {
Notification.success(lll('copyToClipboard.success'), '', 1);
}).catch((): void => {
Notification.error(lll('copyToClipboard.error'));
});
} else {
const textarea = document.createElement('textarea');
textarea.value = this.text;
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
try {
document.execCommand('copy')
? Notification.success(lll('copyToClipboard.success'), '', 1)
: Notification.error(lll('copyToClipboard.error'));
} catch (err) {
Notification.error(lll('copyToClipboard.error'));
}
document.body.removeChild(textarea);
}
}
}
......@@ -18,6 +18,7 @@ import Icons = require('../Icons');
import Modal = require('../Modal');
import Notification = require('../Notification');
import Viewport = require('../Viewport');
import SecurityUtility from 'TYPO3/CMS/Core/SecurityUtility';
enum Identifiers {
containerSelector = '#typo3-cms-backend-backend-toolbaritems-shortcuttoolbaritem',
......@@ -79,12 +80,14 @@ class ShortcutMenu {
this.refreshMenu();
$(Identifiers.toolbarIconSelector, Identifiers.containerSelector).replaceWith($existingIcon);
if (typeof shortcutButton === 'object') {
const isDropdownItem = $(shortcutButton).hasClass('dropdown-item');
const securityUtility = new SecurityUtility();
Icons.getIcon('actions-system-shortcut-active', Icons.sizes.small).then((icon: string): void => {
$(shortcutButton).html(icon);
$(shortcutButton).html(icon + (isDropdownItem ? ' ' + securityUtility.encodeHtml(TYPO3.lang['labels.alreadyBookmarked']) : ''));
});
$(shortcutButton).addClass('active');
$(shortcutButton).attr('title', null);
$(shortcutButton).addClass(isDropdownItem ? 'disabled' : 'active');
$(shortcutButton).attr('onclick', null);
$(shortcutButton).attr('title', TYPO3.lang['labels.alreadyBookmarked']);
}
});
$(e.currentTarget).trigger('modal-dismiss');
......
......@@ -46,6 +46,11 @@ class UriBuilder implements SingletonInterface
*/
const ABSOLUTE_PATH = 'absolute';
/**
* Generates an absolute url for URL sharing
*/
const SHAREABLE_URL = 'share';
/**
* @var Router
*/
......@@ -168,8 +173,8 @@ class UriBuilder implements SingletonInterface
$parameters
);
// If the route has the "public" option set, no token is generated.
if (!$route->hasOption('access') || $route->getOption('access') !== 'public') {
// If the route is not shareable and doesn't have the "public" option set, a token must be generated.
if ($referenceType !== self::SHAREABLE_URL && (!$route->hasOption('access') || $route->getOption('access') !== 'public')) {
$parameters = [
'token' => FormProtectionFactory::get('backend')->generateToken('route', $name)
] + $parameters;
......
......@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Backend\Template\Components\Buttons\Action;
use TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository;
use TYPO3\CMS\Backend\Routing\Router;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
use TYPO3\CMS\Backend\Template\Components\Buttons\ButtonInterface;
use TYPO3\CMS\Backend\Template\Components\Buttons\PositionInterface;
......@@ -25,6 +26,7 @@ use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
......@@ -80,6 +82,11 @@ class ShortcutButton implements ButtonInterface, PositionInterface
*/
protected $getVariables = [];
/**
* @var bool
*/
protected bool $copyUrlToClipboard = true;
/**
* Gets the route identifier for the shortcut.
*
......@@ -210,6 +217,19 @@ class ShortcutButton implements ButtonInterface, PositionInterface
return $this;
}
/**
* Defines whether the shortcut button should be extended to also
* allow copying the current URL to the operating systems' clipboard.
*
* @param bool $copyUrlToClipboard
* @return $this
*/
public function setCopyUrlToClipboard(bool $copyUrlToClipboard): self
{
$this->copyUrlToClipboard = $copyUrlToClipboard;
return $this;
}
/**
* Gets the button position.
*
......@@ -326,25 +346,68 @@ class ShortcutButton implements ButtonInterface, PositionInterface
unset($arguments['returnUrl']);
// Encode arguments to be stored in the database
$arguments = json_encode($arguments) ?: '';
$encodedArguments = json_encode($arguments) ?: '';
if (GeneralUtility::makeInstance(ShortcutRepository::class)->shortcutExists($routeIdentifier, $arguments)) {
return '<a class="active btn btn-default btn-sm" title="">'
. $iconFactory->getIcon('actions-system-shortcut-active', Icon::SIZE_SMALL)->render()
. '</a>';
$confirmationText = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark');
$alreadyBookmarkedText = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.alreadyBookmarked');
if (!$this->copyUrlToClipboard) {
return GeneralUtility::makeInstance(ShortcutRepository::class)->shortcutExists($routeIdentifier, $encodedArguments)
? '<button type="button" class="active btn btn-default btn-sm" title="' . htmlspecialchars($alreadyBookmarkedText) . '">'
. $iconFactory->getIcon('actions-system-shortcut-active', Icon::SIZE_SMALL)->render()
. '</button>'
: '<button type="button" class="btn btn-default btn-sm" title="' . htmlspecialchars($confirmationText) . '" onclick="' . htmlspecialchars($this->getOnClick($routeIdentifier, $encodedArguments, $confirmationText)) . '">'
. $iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL)->render()
. '</button>';
}
$confirmationText = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark');
$onClick = 'top.TYPO3.ShortcutMenu.createShortcut('
. GeneralUtility::quoteJSvalue($routeIdentifier)
. ', ' . GeneralUtility::quoteJSvalue($arguments)
. ', ' . GeneralUtility::quoteJSvalue($this->displayName)
. ', ' . GeneralUtility::quoteJSvalue($confirmationText)
. ', this);return false;';
$menuItems = [];
if (GeneralUtility::makeInstance(ShortcutRepository::class)->shortcutExists($routeIdentifier, $encodedArguments)) {
$menuItems[] =
'<li>' .
'<button type="button" class="dropdown-item btn btn-link disabled">' .
$iconFactory->getIcon('actions-system-shortcut-active', Icon::SIZE_SMALL)->render() . ' ' .
htmlspecialchars($alreadyBookmarkedText) .
'</button>' .
'</li>';
} else {
$menuItems[] = '
<li>' .
'<button type="button" class="dropdown-item btn btn-link" onclick="' . htmlspecialchars($this->getOnClick($routeIdentifier, $encodedArguments, $confirmationText)) . '">' .
$iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL)->render() . ' ' .
htmlspecialchars($confirmationText) .
'</button>' .
'</li>';
}
return '<a href="#" class="btn btn-default btn-sm" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($confirmationText) . '">'
. $iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL)->render()
. '</a>';
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/CopyToClipboard');
$pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_copytoclipboard.xlf');
$currentUrl = (string)GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute(
$routeIdentifier,
$arguments,
UriBuilder::SHAREABLE_URL
);
$menuItems[] = '
<li>
<typo3-copy-to-clipboard text="' . htmlspecialchars($currentUrl) . '">
<button type="button" class="dropdown-item btn btn-link">' .
$iconFactory->getIcon('actions-link', Icon::SIZE_SMALL)->render() . ' ' .
htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.copyCurrentUrl')) .
'</button>' .
'</typo3-copy-to-clipboard>' .
'</li>';
return '
<button type="button" class="btn btn-default btn-sm" id="dropdownShortcutMenu" data-bs-toggle="dropdown" aria-expanded="false" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.share')) . '">' .
$iconFactory->getIcon('share-alt', Icon::SIZE_SMALL)->render() .
'</button>' .
'<ul class="dropdown-menu" aria-labelledby="dropdownShortcutMenu">' .
implode(LF, $menuItems) .
'</ul>';
}
/**
......@@ -405,6 +468,24 @@ class ShortcutButton implements ButtonInterface, PositionInterface
return '';
}
/**
* Return the markup for the onclick attribute of the "add shortcut" button
*
* @param string $routeIdentifier
* @param string $encodedArguments
* @param string $confirmationText
* @return string
*/
protected function getOnClick(string $routeIdentifier, string $encodedArguments, string $confirmationText): string
{
return 'top.TYPO3.ShortcutMenu.createShortcut('
. GeneralUtility::quoteJSvalue($routeIdentifier)
. ', ' . GeneralUtility::quoteJSvalue($encodedArguments)
. ', ' . GeneralUtility::quoteJSvalue($this->displayName)
. ', ' . GeneralUtility::quoteJSvalue($confirmationText)
. ', this);return false;';
}
protected function routeExists(string $routeIdentifier): bool
{
return (bool)($this->getRoutes()[$routeIdentifier] ?? false);
......
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="EXT:backend/Resources/Private/Language/locallang_copytoclipboard.xlf" date="2021-06-01T12:43:12Z" product-name="backend">
<header/>
<body>
<trans-unit id="copyToClipboard.success" resname="copyToClipboard.success">
<source>Copied to clipboard</source>
</trans-unit>
<trans-unit id="copyToClipboard.error" resname="copyToClipboard.error">
<source>Could not be copied to clipboard</source>
</trans-unit>
</body>
</file>
</xliff>
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
var __decorate=this&&this.__decorate||function(e,t,o,r){var c,l=arguments.length,i=l<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,o,r);else for(var n=e.length-1;n>=0;n--)(c=e[n])&&(i=(l<3?c(i):l>3?c(t,o,i):c(t,o))||i);return l>3&&i&&Object.defineProperty(t,o,i),i};define(["require","exports","lit","lit/decorators","TYPO3/CMS/Backend/Notification","TYPO3/CMS/Core/lit-helper"],(function(e,t,o,r,c,l){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let i=class extends o.LitElement{constructor(){super(),this.addEventListener("click",e=>{e.preventDefault(),this.copyToClipboard()})}render(){return o.html`<slot></slot>`}copyToClipboard(){if("string"!=typeof this.text||!this.text.length)return console.warn("No text for copy to clipboard given."),void c.error(l.lll("copyToClipboard.error"));if(navigator.clipboard)navigator.clipboard.writeText(this.text).then(()=>{c.success(l.lll("copyToClipboard.success"),"",1)}).catch(()=>{c.error(l.lll("copyToClipboard.error"))});else{const e=document.createElement("textarea");e.value=this.text,document.body.appendChild(e),e.focus(),e.select();try{document.execCommand("copy")?c.success(l.lll("copyToClipboard.success"),"",1):c.error(l.lll("copyToClipboard.error"))}catch(e){c.error(l.lll("copyToClipboard.error"))}document.body.removeChild(e)}}};__decorate([r.property({type:String})],i.prototype,"text",void 0),i=__decorate([r.customElement("typo3-copy-to-clipboard")],i)}));
\ No newline at end of file
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};define(["require","exports","jquery","TYPO3/CMS/Core/Ajax/AjaxRequest","../Icons","../Modal","../Notification","../Viewport"],(function(t,e,o,r,c,a,s,l){"use strict";var n;o=__importDefault(o),function(t){t.containerSelector="#typo3-cms-backend-backend-toolbaritems-shortcuttoolbaritem",t.toolbarIconSelector=".dropdown-toggle span.icon",t.toolbarMenuSelector=".dropdown-menu",t.shortcutItemSelector=".t3js-topbar-shortcut",t.shortcutDeleteSelector=".t3js-shortcut-delete",t.shortcutEditSelector=".t3js-shortcut-edit",t.shortcutFormTitleSelector='input[name="shortcut-title"]',t.shortcutFormGroupSelector='select[name="shortcut-group"]',t.shortcutFormSaveSelector=".shortcut-form-save",t.shortcutFormCancelSelector=".shortcut-form-cancel",t.shortcutFormSelector=".shortcut-form"}(n||(n={}));let u=new class{constructor(){this.initializeEvents=()=>{o.default(n.containerSelector).on("click",n.shortcutDeleteSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.deleteShortcut(o.default(t.currentTarget).closest(n.shortcutItemSelector))}).on("click",n.shortcutFormGroupSelector,t=>{t.preventDefault(),t.stopImmediatePropagation()}).on("click",n.shortcutEditSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.editShortcut(o.default(t.currentTarget).closest(n.shortcutItemSelector))}).on("click",n.shortcutFormSaveSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.saveShortcutForm(o.default(t.currentTarget).closest(n.shortcutFormSelector))}).on("submit",n.shortcutFormSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.saveShortcutForm(o.default(t.currentTarget).closest(n.shortcutFormSelector))}).on("click",n.shortcutFormCancelSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.refreshMenu()})},l.Topbar.Toolbar.registerEvent(this.initializeEvents)}createShortcut(t,e,s,l,u){void 0!==l&&a.confirm(TYPO3.lang["bookmark.create"],l).on("confirm.button.ok",a=>{const l=o.default(n.toolbarIconSelector,n.containerSelector),i=l.clone();c.getIcon("spinner-circle-light",c.sizes.small).then(t=>{l.replaceWith(t)}),new r(TYPO3.settings.ajaxUrls.shortcut_create).post({routeIdentifier:t,arguments:e,displayName:s}).then(()=>{this.refreshMenu(),o.default(n.toolbarIconSelector,n.containerSelector).replaceWith(i),"object"==typeof u&&(c.getIcon("actions-system-shortcut-active",c.sizes.small).then(t=>{o.default(u).html(t)}),o.default(u).addClass("active"),o.default(u).attr("title",null),o.default(u).attr("onclick",null))}),o.default(a.currentTarget).trigger("modal-dismiss")}).on("confirm.button.cancel",t=>{o.default(t.currentTarget).trigger("modal-dismiss")})}deleteShortcut(t){a.confirm(TYPO3.lang["bookmark.delete"],TYPO3.lang["bookmark.confirmDelete"]).on("confirm.button.ok",e=>{new r(TYPO3.settings.ajaxUrls.shortcut_remove).post({shortcutId:t.data("shortcutid")}).then(()=>{this.refreshMenu()}),o.default(e.currentTarget).trigger("modal-dismiss")}).on("confirm.button.cancel",t=>{o.default(t.currentTarget).trigger("modal-dismiss")})}editShortcut(t){new r(TYPO3.settings.ajaxUrls.shortcut_editform).withQueryArguments({shortcutId:t.data("shortcutid"),shortcutGroup:t.data("shortcutgroup")}).get({cache:"no-cache"}).then(async t=>{o.default(n.containerSelector).find(n.toolbarMenuSelector).html(await t.resolve())})}saveShortcutForm(t){new r(TYPO3.settings.ajaxUrls.shortcut_saveform).post({shortcutId:t.data("shortcutid"),shortcutTitle:t.find(n.shortcutFormTitleSelector).val(),shortcutGroup:t.find(n.shortcutFormGroupSelector).val()}).then(()=>{s.success(TYPO3.lang["bookmark.savedTitle"],TYPO3.lang["bookmark.savedMessage"]),this.refreshMenu()})}refreshMenu(){new r(TYPO3.settings.ajaxUrls.shortcut_list).get({cache:"no-cache"}).then(async t=>{o.default(n.toolbarMenuSelector,n.containerSelector).html(await t.resolve())})}};return TYPO3.ShortcutMenu=u,u}));
\ No newline at end of file
var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};define(["require","exports","jquery","TYPO3/CMS/Core/Ajax/AjaxRequest","../Icons","../Modal","../Notification","../Viewport","TYPO3/CMS/Core/SecurityUtility"],(function(t,e,o,r,c,a,s,l,n){"use strict";var u;o=__importDefault(o),n=__importDefault(n),function(t){t.containerSelector="#typo3-cms-backend-backend-toolbaritems-shortcuttoolbaritem",t.toolbarIconSelector=".dropdown-toggle span.icon",t.toolbarMenuSelector=".dropdown-menu",t.shortcutItemSelector=".t3js-topbar-shortcut",t.shortcutDeleteSelector=".t3js-shortcut-delete",t.shortcutEditSelector=".t3js-shortcut-edit",t.shortcutFormTitleSelector='input[name="shortcut-title"]',t.shortcutFormGroupSelector='select[name="shortcut-group"]',t.shortcutFormSaveSelector=".shortcut-form-save",t.shortcutFormCancelSelector=".shortcut-form-cancel",t.shortcutFormSelector=".shortcut-form"}(u||(u={}));let i=new class{constructor(){this.initializeEvents=()=>{o.default(u.containerSelector).on("click",u.shortcutDeleteSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.deleteShortcut(o.default(t.currentTarget).closest(u.shortcutItemSelector))}).on("click",u.shortcutFormGroupSelector,t=>{t.preventDefault(),t.stopImmediatePropagation()}).on("click",u.shortcutEditSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.editShortcut(o.default(t.currentTarget).closest(u.shortcutItemSelector))}).on("click",u.shortcutFormSaveSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.saveShortcutForm(o.default(t.currentTarget).closest(u.shortcutFormSelector))}).on("submit",u.shortcutFormSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.saveShortcutForm(o.default(t.currentTarget).closest(u.shortcutFormSelector))}).on("click",u.shortcutFormCancelSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.refreshMenu()})},l.Topbar.Toolbar.registerEvent(this.initializeEvents)}createShortcut(t,e,s,l,i){void 0!==l&&a.confirm(TYPO3.lang["bookmark.create"],l).on("confirm.button.ok",a=>{const l=o.default(u.toolbarIconSelector,u.containerSelector),h=l.clone();c.getIcon("spinner-circle-light",c.sizes.small).then(t=>{l.replaceWith(t)}),new r(TYPO3.settings.ajaxUrls.shortcut_create).post({routeIdentifier:t,arguments:e,displayName:s}).then(()=>{if(this.refreshMenu(),o.default(u.toolbarIconSelector,u.containerSelector).replaceWith(h),"object"==typeof i){const t=o.default(i).hasClass("dropdown-item"),e=new n.default;c.getIcon("actions-system-shortcut-active",c.sizes.small).then(r=>{o.default(i).html(r+(t?" "+e.encodeHtml(TYPO3.lang["labels.alreadyBookmarked"]):""))}),o.default(i).addClass(t?"disabled":"active"),o.default(i).attr("onclick",null),o.default(i).attr("title",TYPO3.lang["labels.alreadyBookmarked"])}}),o.default(a.currentTarget).trigger("modal-dismiss")}).on("confirm.button.cancel",t=>{o.default(t.currentTarget).trigger("modal-dismiss")})}deleteShortcut(t){a.confirm(TYPO3.lang["bookmark.delete"],TYPO3.lang["bookmark.confirmDelete"]).on("confirm.button.ok",e=>{new r(TYPO3.settings.ajaxUrls.shortcut_remove).post({shortcutId:t.data("shortcutid")}).then(()=>{this.refreshMenu()}),o.default(e.currentTarget).trigger("modal-dismiss")}).on("confirm.button.cancel",t=>{o.default(t.currentTarget).trigger("modal-dismiss")})}editShortcut(t){new r(TYPO3.settings.ajaxUrls.shortcut_editform).withQueryArguments({shortcutId:t.data("shortcutid"),shortcutGroup:t.data("shortcutgroup")}).get({cache:"no-cache"}).then(async t=>{o.default(u.containerSelector).find(u.toolbarMenuSelector).html(await t.resolve())})}saveShortcutForm(t){new r(TYPO3.settings.ajaxUrls.shortcut_saveform).post({shortcutId:t.data("shortcutid"),shortcutTitle:t.find(u.shortcutFormTitleSelector).val(),shortcutGroup:t.find(u.shortcutFormGroupSelector).val()}).then(()=>{s.success(TYPO3.lang["bookmark.savedTitle"],TYPO3.lang["bookmark.savedMessage"]),this.refreshMenu()})}refreshMenu(){new r(TYPO3.settings.ajaxUrls.shortcut_list).get({cache:"no-cache"}).then(async t=>{o.default(u.toolbarMenuSelector,u.containerSelector).html(await t.resolve())})}};return TYPO3.ShortcutMenu=i,i}));
\ No newline at end of file
......@@ -63,18 +63,31 @@ class ShortcutButtonTest extends FunctionalTestCase
public function rendersCorrectMarkupDataProvider(): \Generator
{
yield 'Recordlist' => [
(new ShortcutButton())->setRouteIdentifier('web_list')->setDisplayName('Recordlist'),
(new ShortcutButton())
->setRouteIdentifier('web_list')
->setDisplayName('Recordlist')
->setCopyUrlToClipboard(false),
'RecordList'
];
yield 'Recordlist with copyToClipboard action' => [
(new ShortcutButton())
->setRouteIdentifier('web_list')
->setDisplayName('Recordlist'),
'RecordListCopyToClipboard'
];
// @todo Below is deprecated functionality which only provides backwards compatibility for v11. Remove in v12!
yield 'Recordlist as route path' => [
(new ShortcutButton())->setRouteIdentifier('/module/web/list')->setDisplayName('Recordlist'),
(new ShortcutButton())
->setRouteIdentifier('/module/web/list')
->setDisplayName('Recordlist')
->setCopyUrlToClipboard(false),
'RecordList'
];
yield 'Recordlist - single table view' => [
(new ShortcutButton())
->setRouteIdentifier('web_list')
->setDisplayName('Recordlist - single table view')
->setCopyUrlToClipboard(false)
->setArguments([
'id' => 123,
'table' => 'some_table',
......@@ -84,14 +97,31 @@ class ShortcutButtonTest extends FunctionalTestCase
]),
'RecordListSingleTable'
];
yield 'Recordlist - single table view with copyToClipboard action' => [
(new ShortcutButton())
->setRouteIdentifier('web_list')
->setDisplayName('Recordlist - single table view')
->setArguments([
'id' => 123,
'table' => 'some_table',
'GET' => [
'clipBoard' => 1
]
]),
'RecordListSingleTableCopyToClipboard'
];
yield 'With special route identifier' => [
(new ShortcutButton())->setRouteIdentifier('record_edit')->setDisplayName('Edit record'),
(new ShortcutButton())
->setRouteIdentifier('record_edit')
->setDisplayName('Edit record')
->setCopyUrlToClipboard(false),
'SpecialRouteIdentifier'
];
yield 'With special route identifier and arguments' => [
(new ShortcutButton())
->setRouteIdentifier('record_edit')
->setDisplayName('Edit record')
->setCopyUrlToClipboard(false)
->setArguments([
'id' => 123,
'edit' => [
......@@ -108,14 +138,40 @@ class ShortcutButtonTest extends FunctionalTestCase
]),
'SpecialRouteIdentifierWithArguments'
];
yield 'With special route identifier and arguments - copyToClipboard' => [
(new ShortcutButton())
->setRouteIdentifier('record_edit')
->setDisplayName('Edit record')
->setArguments([
'id' => 123,
'edit' => [
'pages' => [
123 => 'edit',
],
'overrideVals' => [
'pages' => [
'sys_language_uid' => 1
]
]
],
'returnUrl' => 'some/url'
]),
'SpecialRouteIdentifierWithArgumentsCopyToClipboard'
];
// @todo Below is deprecated functionality which only provides backwards compatibility for v11. Remove in v12!
yield 'With special route path' => [
(new ShortcutButton())->setRouteIdentifier('/record/edit')->setDisplayName('Edit record'),
(new ShortcutButton())
->setRouteIdentifier('/record/edit')
->setDisplayName('Edit record')
->setCopyUrlToClipboard(false),
'SpecialRouteIdentifier'
];
// @todo Below is deprecated functionality which only provides backwards compatibility for v11. Remove in v12!
yield 'With special route path as Argument' => [
(new ShortcutButton())->setArguments(['route' => '/record/edit'])->setDisplayName('Edit record'),
(new ShortcutButton())
->setArguments(['route' => '/record/edit'])
->setDisplayName('Edit record')
->setCopyUrlToClipboard(false),
'SpecialRouteIdentifier'
];
}
......
<a href="#"
<button type="button"
class="btn btn-default btn-sm"
onclick="top.TYPO3.ShortcutMenu.createShortcut('web_list', '[]', 'Recordlist', 'Create\u0020a\u0020bookmark\u0020to\u0020this\u0020page', this);return false;"
title="Create a bookmark to this page">
title="Create a bookmark to this page"
onclick="top.TYPO3.ShortcutMenu.createShortcut('web_list', '[]', 'Recordlist', 'Create\u0020a\u0020bookmark\u0020to\u0020this\u0020page', this);return false;">
<span class="t3js-icon icon icon-size-small icon-state-default icon-actions-system-shortcut-new" data-identifier="actions-system-shortcut-new">
<span class="icon-markup">
<svg class="icon-color"><use xlink:href="typo3/sysext/core/Resources/Public/Icons/T3Icons/sprites/actions.svg#actions-star" /></svg>
</span>
</span>
</a>
</button>
<button type="button"
class="btn btn-default btn-sm"
id="dropdownShortcutMenu"
data-bs-toggle="dropdown"
aria-expanded="false"
title="Share">
<span class="t3js-icon icon icon-size-small icon-state-default icon-share-alt" data-identifier="share-alt">
<span class="icon-markup">
<span class="icon-unify">
<i class="fa fa-share-alt"></i>
</span>
</span>
</span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownShortcutMenu">
<li>
<button type="button"
class="dropdown-item btn btn-link"
onclick="top.TYPO3.ShortcutMenu.createShortcut('web_list', '[]', 'Recordlist', 'Create\u0020a\u0020bookmark\u0020to\u0020this\u0020page', this);return false;">
<span class="t3js-icon icon icon-size-small icon-state-default icon-actions-system-shortcut-new" data-identifier="actions-system-shortcut-new">
<span class="icon-markup">
<svg class="icon-color"><use xlink:href="typo3/sysext/core/Resources/Public/Icons/T3Icons/sprites/actions.svg#actions-star" /></svg>
</span>
</span>
Create a bookmark to this page
</button>
</li>
<li>
<typo3-copy-to-clipboard text="http://vendor/phpunit/phpunit/module/web/list">
<button type="button" class="dropdown-item btn btn-link">
<span class="t3js-icon icon icon-size-small icon-state-default icon-actions-link" data-identifier="actions-link">
<span class="icon-markup">
<svg class="icon-color"><use xlink:href="typo3/sysext/core/Resources/Public/Icons/T3Icons/sprites/actions.svg#actions-link" /></svg>
</span>
</span>
Copy URL of this page
</button>
</typo3-copy-to-clipboard>
</li>
</ul>
<a href="#"
<button type="button"
class="btn btn-default btn-sm"
onclick="top.TYPO3.ShortcutMenu.createShortcut('web_list', '{\u0022id\u0022:123,\u0022table\u0022:\u0022some_table\u0022,\u0022GET\u0022:{\u0022clipBoard\u0022:1}}', 'Recordlist\u0020-\u0020single\u0020table\u0020view', 'Create\u0020a\u0020bookmark\u0020to\u0020this\u0020page', this);return false;"
title="Create a bookmark to this page">
title="Create a bookmark to this page"
onclick="top.TYPO3.ShortcutMenu.createShortcut('web_list', '{\u0022id\u0022:123,\u0022table\u0022:\u0022some_table\u0022,\u0022GET\u0022:{\u0022clipBoard\u0022:1}}', 'Recordlist\u0020-\u0020single\u0020table\u0020view', 'Create\u0020a\u0020bookmark\u0020to\u0020this\u0020page', this);return false;">
<span class="t3js-icon icon icon-size-small icon-state-default icon-actions-system-shortcut-new" data-identifier="actions-system-shortcut-new">
<span class="icon-markup">
<svg class="icon-color"><use xlink:href="typo3/sysext/core/Resources/Public/Icons/T3Icons/sprites/actions.svg#actions-star" /></svg>
</span>
</span>
</a>
</button>
<button type="button"
class="btn btn-default btn-sm"
id="dropdownShortcutMenu"
data-bs-toggle="dropdown"
aria-expanded="false"
title="Share">
<span class="t3js-icon icon icon-size-small icon-state-default icon-share-alt" data-identifier="share-alt">
<span class="icon-markup">
<span class="icon-unify">
<i class="fa fa-share-alt"></i>
</span>
</span>
</span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownShortcutMenu">
<li>
<button type="button"
class="dropdown-item btn btn-link"