Skip to content

Commit

Permalink
Copy plugin handle/package name actions
Browse files Browse the repository at this point in the history
Resolves #16281
  • Loading branch information
brandonkelly committed Dec 7, 2024
1 parent f49a06b commit 8bdff83
Show file tree
Hide file tree
Showing 20 changed files with 320 additions and 164 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/templates/_includes/disclosuremenu.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
131 changes: 102 additions & 29 deletions src/templates/settings/plugins/index.twig
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,84 @@
{% for handle, config in info %}
{% set pluginStoreUrl = url('plugin-store/' ~ handle) %}
{% set forceDisabled = disabledPlugins == '*' or handle in disabledPlugins %}
<tr id="plugin-{{ handle }}" data-name="{{ config.name }}" data-handle="{{ handle }}">

{% 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,
},
} %}
<th>
<div class="plugin-infos">
<a class="icon" href="{{ pluginStoreUrl }}" title="{{ 'View {plugin} in the Plugin Store'|t('app', {plugin: config.name}) }}" title="{{ 'View {plugin} in the Plugin Store'|t('app', {plugin: config.name}) }}">
Expand Down Expand Up @@ -139,39 +216,35 @@
<span class="status on"></span>{{ "Installed"|t('app') }}
{% elseif config.isInstalled %}
<span class="status off"></span>{{ "Disabled"|t('app') }}
{% if forceDisabled %}
<span class="info">
{{ '{plugin} is disabled by the {setting} config setting.'|t({
plugin: config.name,
setting: 'disabledPlugins',
}) }}
</span>
{% endif %}
{% else %}
<span class="status disabled"></span><span class="light">{{ "Not installed"|t('app') }}</span>
{% if forceDisabled %}
<span class="info">
{{ '{plugin} can’t be installed due to the {setting} config setting.'|t({
plugin: config.name,
setting: 'disabledPlugins',
}) }}
</span>
{% endif %}
{% endif %}
</td>
<td class="nowrap thin" data-title="{{ 'Action'|t('app') }}">
<form method="post" accept-charset="UTF-8">
{{ hiddenInput('pluginHandle', handle) }}
{{ csrfInput() }}
<button type="button" class="btn menubtn action-btn hairline" aria-label="{{ 'Settings'|t('app') }}"></button>
<div class="menu" data-align="right">
<ul>
{% if not config.isInstalled %}
{% if forceDisabled %}
<li><a class="disabled" title="{{ '{plugin} can’t be installed due to the {setting} config setting.'|t({plugin: config.name, setting: 'disabledPlugins'}) }}">{{ 'Install'|t('app') }}</a></li>
{% else %}
<li><a class="formsubmit" data-action="plugins/install-plugin">{{ 'Install'|t('app') }}</a></li>
{% endif %}
<li><a class="formsubmit error" data-action="pluginstore/remove" data-param="packageName" data-value="{{ config.packageName }}">{{ 'Remove'|t('app') }}</a></li>
{% else %}
{% if config.isEnabled %}
<li><a class="formsubmit" data-action="plugins/disable-plugin">{{ 'Disable'|t('app') }}</a></li>
<li><a class="formsubmit error" data-action="plugins/uninstall-plugin" data-confirm="{{ 'Are you sure you want to uninstall {plugin}? You will lose all of its associated data.'|t('app', { plugin: config.name }) }}">{{ 'Uninstall'|t('app') }}</a></li>
{% elseif forceDisabled %}
<li><a class="disabled" title="{{ '{plugin} is disabled by the {setting} config setting.'|t({plugin: config.name, setting: 'disabledPlugins'}) }}">{{ 'Enable'|t('app') }}</a></li>
{% else %}
<li><a class="formsubmit" data-action="plugins/enable-plugin">{{ 'Enable'|t('app') }}</a></li>
{% endif %}
{% endif %}
</ul>
</div>
</form>
<td class="nowrap thin" data-title="{{ 'Actions'|t('app') }}">
{{ disclosureMenu(actionItems, {
buttonAttributes: {
class: ['action-btn', 'hairline'],
hiddenLabel: 'Actions'|t('app'),
},
}) }}
</td>
</tr>
{% endtag %}
{% endfor %}
</tbody>
</table>
Expand Down
4 changes: 4 additions & 0 deletions src/translations/en/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion src/web/CpScreenResponseFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
],
]),
Expand Down
2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/cp.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/cp.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/css/cp.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/css/cp.css.map

Large diffs are not rendered by default.

17 changes: 10 additions & 7 deletions src/web/assets/cp/src/css/_cp.scss
Original file line number Diff line number Diff line change
Expand Up @@ -372,12 +372,20 @@ a#system-info {
li.crumb .btn.menubtn,
.btn.action-btn {
padding: 0;
width: var(--touch-target-size);
min-height: var(--touch-target-size);

&:not(:hover, :active, [aria-expanded='true']) {
background-color: transparent;
}

&:not(.m) {
width: var(--touch-target-size);
min-height: var(--touch-target-size);
}

&.m {
width: var(--ui-control-height);
height: var(--ui-control-height);
}
}

#global-header {
Expand Down Expand Up @@ -1001,11 +1009,6 @@ li.breadcrumb-toggle-wrapper {
#action-buttons {
flex-shrink: 0;

.action-btn {
width: var(--ui-control-height);
height: var(--ui-control-height);
}

@media only screen and (width <= 400px) {
.btngroup .btn:first-child {
flex-basis: 100%;
Expand Down
54 changes: 29 additions & 25 deletions src/web/assets/cp/src/js/CP.js
Original file line number Diff line number Diff line change
Expand Up @@ -1267,7 +1267,7 @@ Craft.CP = Garnish.Base.extend(
}
},

checkForUpdates: function (
checkForUpdates: async function (
forceRefresh,
includeDetails,
onSuccess,
Expand Down Expand Up @@ -1318,36 +1318,40 @@ Craft.CP = Garnish.Base.extend(
this.forcingRefreshOnUpdatesCheck = forceRefresh === true;
this.includingDetailsOnUpdatesCheck = includeDetails === true;

this._checkForUpdates(forceRefresh, includeDetails)
.then((info) => {
this.updateUtilitiesBadge();
this.checkingForUpdates = false;
let info;

if (Array.isArray(this.checkForUpdatesCallbacks)) {
const callbacks = this.checkForUpdatesCallbacks;
this.checkForUpdatesCallbacks = null;
try {
info = await this._checkForUpdates(forceRefresh, includeDetails);
} catch (e) {
this.checkingForUpdates = false;

for (let callback of callbacks) {
callback(info);
}
if (Array.isArray(this.checkForUpdatesFailureCallbacks)) {
const callbacks = this.checkForUpdatesFailureCallbacks;
this.checkForUpdatesFailureCallbacks = null;

for (let callback of callbacks) {
callback();
}
}

this.trigger('checkForUpdates', {
updateInfo: info,
});
})
.catch(() => {
this.checkingForUpdates = false;
return;
}

if (Array.isArray(this.checkForUpdatesFailureCallbacks)) {
const callbacks = this.checkForUpdatesFailureCallbacks;
this.checkForUpdatesFailureCallbacks = null;
this.updateUtilitiesBadge();
this.checkingForUpdates = false;

for (let callback of callbacks) {
callback();
}
}
});
if (Array.isArray(this.checkForUpdatesCallbacks)) {
const callbacks = this.checkForUpdatesCallbacks;
this.checkForUpdatesCallbacks = null;

for (let callback of callbacks) {
callback(info);
}
}

this.trigger('checkForUpdates', {
updateInfo: info,
});
}
},

Expand Down
2 changes: 1 addition & 1 deletion src/web/assets/garnish/dist/garnish.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/garnish/dist/garnish.js.map

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/web/assets/garnish/src/DisclosureMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -524,8 +524,12 @@ export default Base.extend(
el.classList.add('error');
el.setAttribute('data-destructive', 'true');
}
if (item.disabled) {
el.classList.add('disabled');
}
if (item.action) {
el.classList.add('formsubmit');
$(el).formsubmit();
}
if (type === 'link') {
el.href = Craft.getUrl(item.url);
Expand Down
4 changes: 4 additions & 0 deletions src/web/assets/plugins/PluginsAsset.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,13 @@ public function registerAssetFiles($view): void
'<a>Renew now</a> for another year of updates.',
'A license key is required.',
'Action',
'Copy package name',
'Copy plugin handle',
'Documentation',
'Install',
'Missing',
'Package Name',
'Plugin Handle',
'Plugin trials are not allowed on this domain.',
'Status',
'Switch',
Expand Down
Loading

0 comments on commit 8bdff83

Please sign in to comment.