From 8bdff8362c1631cb4de400b3feace218e31e5a38 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Sat, 7 Dec 2024 13:12:06 -0800 Subject: [PATCH] Copy plugin handle/package name actions Resolves #16281 --- CHANGELOG-WIP.md | 3 + src/templates/_includes/disclosuremenu.twig | 1 + src/templates/settings/plugins/index.twig | 131 +++++++++--- src/translations/en/app.php | 4 + src/web/CpScreenResponseFormatter.php | 2 +- src/web/assets/cp/dist/cp.js | 2 +- src/web/assets/cp/dist/cp.js.map | 2 +- src/web/assets/cp/dist/css/cp.css | 2 +- src/web/assets/cp/dist/css/cp.css.map | 2 +- src/web/assets/cp/src/css/_cp.scss | 17 +- src/web/assets/cp/src/js/CP.js | 54 ++--- src/web/assets/garnish/dist/garnish.js | 2 +- src/web/assets/garnish/dist/garnish.js.map | 2 +- src/web/assets/garnish/src/DisclosureMenu.js | 4 + src/web/assets/plugins/PluginsAsset.php | 4 + src/web/assets/plugins/dist/PluginManager.js | 2 +- .../assets/plugins/dist/PluginManager.js.map | 2 +- src/web/assets/plugins/src/PluginManager.js | 188 ++++++++++-------- src/web/assets/updates/UpdatesAsset.php | 4 + src/web/assets/updates/src/UpdatesUtility.js | 56 +++++- 20 files changed, 320 insertions(+), 164 deletions(-) diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md index 15f787288b9..60cdee39031 100644 --- a/CHANGELOG-WIP.md +++ b/CHANGELOG-WIP.md @@ -12,6 +12,7 @@ - Improved the accessibility of control panel icons. ([#16128](https://github.com/craftcms/cms/pull/16128)) - Improved the accessibility of Selectize inputs. ([#16110](https://github.com/craftcms/cms/pull/16110)) - Improved the accessibility of the image rotation control within the Image Editor. ([#16218](https://github.com/craftcms/cms/pull/16218)) +- Improved the accessibility of action menus on the Plugins index page. ### Administration - Added the “Show the ‘URL Suffix’ field” setting to Link fields. ([#15813](https://github.com/craftcms/cms/discussions/15813)) @@ -21,6 +22,8 @@ - Added the “GraphQL Mode” Link field setting. ([#16237](https://github.com/craftcms/cms/pull/16237)) - Added the “Field” entry condition rule, which replaces “Matrix field”, includes a “has a value” operator. ([#16270](https://github.com/craftcms/cms/discussions/16270)) - Section condition rules now have a “has a value” operator. ([#16270](https://github.com/craftcms/cms/discussions/16270)) +- Added “Copy plugin handle” and “Copy package name” options to plugins’ action menus on the Plugins index page. ([#16281](https://github.com/craftcms/cms/discussions/16281)) +- The Updates utility now shows an action menu for each plugin, with “Copy plugin handle” and “Copy package name” options. ([#16281](https://github.com/craftcms/cms/discussions/16281)) - The Queue Manager utility now shows jobs’ class names. ([#16228](https://github.com/craftcms/cms/pull/16228)) - Improved the wording of field instance action labels. ([#16261](https://github.com/craftcms/cms/discussions/16261)) - Improved the error output for nested elements when they can’t be resaved via `resave` commands. diff --git a/src/templates/_includes/disclosuremenu.twig b/src/templates/_includes/disclosuremenu.twig index 3d436fa1ad6..2ba8d4e851c 100644 --- a/src/templates/_includes/disclosuremenu.twig +++ b/src/templates/_includes/disclosuremenu.twig @@ -38,6 +38,7 @@ sel: selected, error: item.destructive ?? false, formsubmit: item.action ?? false, + disabled: item.disabled ?? false, }|filter|keys, href: type == 'button' ? null : url(item.url), data: { diff --git a/src/templates/settings/plugins/index.twig b/src/templates/settings/plugins/index.twig index 6e52ebd1501..f1cc1219e4c 100644 --- a/src/templates/settings/plugins/index.twig +++ b/src/templates/settings/plugins/index.twig @@ -26,7 +26,84 @@ {% for handle, config in info %} {% set pluginStoreUrl = url('plugin-store/' ~ handle) %} {% set forceDisabled = disabledPlugins == '*' or handle in disabledPlugins %} - + + {% set actionItems = [ + { + icon: 'clipboard', + label: 'Copy plugin handle', + attributes: { + data: { + action: 'copy-plugin-handle', + }, + }, + }, + { + icon: 'clipboard', + label: 'Copy package name', + attributes: { + data: { + action: 'copy-package-name', + }, + }, + }, + {type: 'hr'}, + ] %} + {% if not config.isInstalled %} + {% set actionItems = actionItems|push( + { + icon: 'plus', + label: 'Install'|t('app'), + action: 'plugins/install-plugin', + params: {pluginHandle: handle}, + disabled: forceDisabled + }, + { + icon: 'minus', + label: 'Remove'|t('app'), + action: 'pluginstore/remove', + params: {packageName: config.packageName}, + destructive: true, + }, + ) %} + {% else %} + {% if config.isEnabled %} + {% set actionItems = actionItems|push( + { + icon: 'circle-dashed', + label: 'Disable'|t('app'), + action: 'plugins/disable-plugin', + params: {pluginHandle: handle}, + }, + { + icon: 'xmark', + label: 'Uninstall'|t('app'), + action: 'plugins/uninstall-plugin', + params: {pluginHandle: handle}, + destructive: true, + confirm: 'Are you sure you want to uninstall {plugin}? You will lose all of its associated data.'|t('app', { + plugin: config.name, + }), + } + ) %} + {% else %} + {% set actionItems = actionItems|push({ + icon: 'circle', + label: 'Enable'|t('app'), + action: 'plugins/enable-plugin', + params: {pluginHandle: handle}, + disabled: forceDisabled, + }) %} + {% endif %} + {% endif %} + + {% tag 'tr' with { + id: "plugin-#{handle}", + data: { + name: config.name, + handle: handle, + 'package-name': config.packageName, + }, + } %}
@@ -139,39 +216,35 @@ {{ "Installed"|t('app') }} {% elseif config.isInstalled %} {{ "Disabled"|t('app') }} + {% if forceDisabled %} + + {{ '{plugin} is disabled by the {setting} config setting.'|t({ + plugin: config.name, + setting: 'disabledPlugins', + }) }} + + {% endif %} {% else %} {{ "Not installed"|t('app') }} + {% if forceDisabled %} + + {{ '{plugin} can’t be installed due to the {setting} config setting.'|t({ + plugin: config.name, + setting: 'disabledPlugins', + }) }} + + {% endif %} {% endif %} - -
- {{ hiddenInput('pluginHandle', handle) }} - {{ csrfInput() }} - - -
+ + {{ disclosureMenu(actionItems, { + buttonAttributes: { + class: ['action-btn', 'hairline'], + hiddenLabel: 'Actions'|t('app'), + }, + }) }} - + {% endtag %} {% endfor %} diff --git a/src/translations/en/app.php b/src/translations/en/app.php index d586c182922..24b841ef599 100644 --- a/src/translations/en/app.php +++ b/src/translations/en/app.php @@ -348,7 +348,9 @@ 'Copy URL' => 'Copy URL', 'Copy activation URL…' => 'Copy activation URL…', 'Copy impersonation URL…' => 'Copy impersonation URL…', + 'Copy package name' => 'Copy package name', 'Copy password reset URL…' => 'Copy password reset URL…', + 'Copy plugin handle' => 'Copy plugin handle', 'Copy reference tag' => 'Copy reference tag', 'Copy the URL' => 'Copy the URL', 'Copy the activation URL' => 'Copy the activation URL', @@ -1142,6 +1144,7 @@ 'Our site is temporarily unavailable. Please try again later.' => 'Our site is temporarily unavailable. Please try again later.', 'Overview' => 'Overview', 'PHP Info' => 'PHP Info', + 'Package Name' => 'Package Name', 'Page Not Found' => 'Page Not Found', 'Page not found.' => 'Page not found.', 'Pagination' => 'Pagination', @@ -1180,6 +1183,7 @@ 'Please fix the following in your {file} file before proceeding:' => 'Please fix the following in your {file} file before proceeding:', 'Please set a valid volume for storing the user photos in user settings page first.' => 'Please set a valid volume for storing the user photos in user settings page first.', 'Please talk to your host/IT department about upgrading your server.' => 'Please talk to your host/IT department about upgrading your server.', + 'Plugin Handle' => 'Plugin Handle', 'Plugin Installer' => 'Plugin Installer', 'Plugin Store' => 'Plugin Store', 'Plugin Uninstaller' => 'Plugin Uninstaller', diff --git a/src/web/CpScreenResponseFormatter.php b/src/web/CpScreenResponseFormatter.php index 1c5b6ab65a3..c764d2d530c 100644 --- a/src/web/CpScreenResponseFormatter.php +++ b/src/web/CpScreenResponseFormatter.php @@ -179,7 +179,7 @@ private function _formatTemplate(YiiResponse $response, CpScreenResponseBehavior 'hiddenLabel' => Craft::t('app', 'Actions'), 'buttonAttributes' => [ 'id' => 'action-btn', - 'class' => ['action-btn', 'hairline-dark'], + 'class' => ['action-btn', 'hairline-dark', 'm'], 'title' => Craft::t('app', 'Actions'), ], ]), diff --git a/src/web/assets/cp/dist/cp.js b/src/web/assets/cp/dist/cp.js index e3cb50fe533..4ef271f6c7a 100644 --- a/src/web/assets/cp/dist/cp.js +++ b/src/web/assets/cp/dist/cp.js @@ -1,3 +1,3 @@ /*! For license information please see cp.js.LICENSE.txt */ -(function(){var __webpack_modules__={463:function(){Craft.Accordion=Garnish.Base.extend({$trigger:null,targetSelector:null,_$target:null,init:function(t){var e=this;this.$trigger=$(t),this.$trigger.data("accordion")&&(console.warn("Double-instantiating an accordion trigger on an element"),this.$trigger.data("accordion").destroy()),this.$trigger.data("accordion",this),this.targetSelector=this.$trigger.attr("aria-controls")?"#".concat(this.$trigger.attr("aria-controls")):null,this.targetSelector&&(this._$target=$(this.targetSelector)),this.addListener(this.$trigger,"click","onTriggerClick"),this.addListener(this.$trigger,"keypress",(function(t){var n=t.keyCode;n!==Garnish.SPACE_KEY&&n!==Garnish.RETURN_KEY||(t.preventDefault(),e.onTriggerClick())}))},onTriggerClick:function(){"true"===this.$trigger.attr("aria-expanded")?this.hideTarget(this._$target):this.showTarget(this._$target)},showTarget:function(t){var e=this;if(t&&t.length){this.showTarget._currentHeight=t.height(),t.removeClass("hidden"),this.$trigger.removeClass("collapsed").addClass("expanded").attr("aria-expanded","true");for(var n=0;n=this.settings.maxItems)){var e=$(t).appendTo(this.$tbody),n=e.find(".delete");this.settings.sortable&&this.sorter.addItems(e),this.$deleteBtns=this.$deleteBtns.add(n),this.addListener(n,"click","handleDeleteBtnClick"),this.totalItems++,this.updateUI()}},reorderItems:function(){var t=this;if(this.settings.sortable){for(var e=[],n=0;n=this.settings.maxItems?$(this.settings.newItemBtnSelector).addClass("hidden"):$(this.settings.newItemBtnSelector).removeClass("hidden"))}},{defaults:{tableSelector:null,noItemsSelector:null,newItemBtnSelector:null,idAttribute:"data-id",nameAttribute:"data-name",sortable:!1,allowDeleteAll:!0,minItems:0,maxItems:null,reorderAction:null,deleteAction:null,reorderSuccessMessage:Craft.t("app","New order saved."),reorderFailMessage:Craft.t("app","Couldn’t save new order."),confirmDeleteMessage:Craft.t("app","Are you sure you want to delete “{name}”?"),deleteSuccessMessage:Craft.t("app","“{name}” deleted."),deleteFailMessage:Craft.t("app","Couldn’t delete “{name}”."),onReorderItems:$.noop,onDeleteItem:$.noop}})},6872:function(){Craft.AssetImageEditor=Garnish.Modal.extend({$body:null,$footer:null,$imageTools:null,$buttons:null,$cancelBtn:null,$replaceBtn:null,$saveBtn:null,$focalPointBtn:null,$editorContainer:null,$straighten:null,$croppingCanvas:null,$spinner:null,$constraintContainer:null,$constraintRadioInputs:null,$customConstraints:null,canvas:null,image:null,viewport:null,focalPoint:null,focalPointInnerCircle:null,focalPointOuterCircle:null,focalPointPickedIndicator:null,grid:null,croppingCanvas:null,clipper:null,croppingRectangle:null,cropperHandles:null,cropperGrid:null,croppingShade:null,imageStraightenAngle:0,viewportRotation:0,originalWidth:0,originalHeight:0,imageVerticeCoords:null,zoomRatio:1,animationInProgress:!1,currentView:"",assetId:null,cacheBust:null,draggingCropper:!1,scalingCropper:!1,handleClicked:!1,draggingFocal:!1,focalPickedUp:!1,focalClicked:!1,cropperClicked:!1,previousMouseX:0,previousMouseY:0,shiftKeyHeld:!1,editorHeight:0,editorWidth:0,cropperState:!1,scaleFactor:1,flipData:{},focalPointState:!1,maxImageSize:null,lastLoadedDimensions:null,imageIsLoading:!1,mouseMoveEvent:null,croppingConstraint:!1,constraintOrientation:"landscape",showingCustomConstraint:!1,saving:!1,renderImage:null,renderCropper:null,_queue:null,init:function(t,e){var n=this;this._queue=new Craft.Queue,this.cacheBust=Date.now(),this.setSettings(e,Craft.AssetImageEditor.defaults),null===this.settings.allowDegreeFractions&&(this.settings.allowDegreeFractions=Craft.isImagick),Garnish.prefersReducedMotion()&&(this.settings.animationDuration=1),this.assetId=t,this.flipData={x:0,y:0},this.$container=$('').appendTo(Garnish.$bod),this.$body=$('
').appendTo(this.$container),this.$footer=$('