Skip to content

Commit 7c6c0d8

Browse files
committed
"Paste above" actions
1 parent 597d862 commit 7c6c0d8

10 files changed

Lines changed: 116 additions & 18 deletions

File tree

CHANGELOG-WIP.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
### Content Management
44
- Collapsed Matrix blocks now show their entries’ UI labels as preview text, whenever possible. ([#18484](https://github.com/craftcms/cms/discussions/18484))
55
- Element-level actions within nested element management fields (Matrix, Addresses, etc.) now consistently affect all selected elements, when performed on a selected element. ([#18561](https://github.com/craftcms/cms/pull/18561))
6+
- Elements within Matrix and Addresses fields now have “Paste above” actions when a compatible element is copied. ([#17406](https://github.com/craftcms/cms/discussions/17406))
67
- Addresses fields now have a “Copy all addresses” field-level action. ([#18561](https://github.com/craftcms/cms/pull/18561))
78
- Matrix fields’ “Expand”, “Collapse”, and “Copy” field-level actions now always affect all nested entries, regardless of whether any entries are selected. ([#18561](https://github.com/craftcms/cms/pull/18561))
89
- Matrix fields no longer have “Duplicate” and “Delete” field-level actions. ([#18561](https://github.com/craftcms/cms/pull/18561))

src/templates/_components/fieldtypes/Matrix/block.twig

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,17 +120,30 @@
120120
]) %}
121121
{% endif %}
122122

123-
{% set actionMenuItems = actionMenuItems|merge([
124-
{
125-
icon: 'clone-dashed',
123+
{% set actionMenuItems = actionMenuItems|push({
124+
icon: 'clone-dashed',
125+
color: 'fuchsia',
126+
label: 'Copy'|t('app'),
127+
attributes: {
128+
data: {action: 'copy'},
129+
},
130+
}) %}
131+
132+
{% if not staticEntries %}
133+
{% set actionMenuItems = actionMenuItems|push({
134+
icon: 'duplicate',
126135
color: 'fuchsia',
127-
label: 'Copy'|t('app'),
136+
label: 'Paste {type} above'|t('app', {type: 'entry'|t('app')}),
128137
attributes: {
129-
data: {action: 'copy'},
138+
data: {action: 'paste'},
130139
},
131-
},
132-
{hr: true},
133-
]) %}
140+
liAttributes: {
141+
class: ['hidden'],
142+
},
143+
}) %}
144+
{% endif %}
145+
146+
{% set actionMenuItems = actionMenuItems|push({hr: true}) %}
134147

135148
{% if not staticEntries %}
136149
{% for entryType in entryTypes %}

src/translations/en/app.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,8 @@
12431243
'Password' => 'Password',
12441244
'Past year' => 'Past year',
12451245
'Past {num} days' => 'Past {num} days',
1246+
'Paste {type} above' => 'Paste {type} above',
1247+
'Paste {type} before' => 'Paste {type} before',
12461248
'Paste {type}' => 'Paste {type}',
12471249
'Pay {price}' => 'Pay {price}',
12481250
'Pending' => 'Pending',

src/web/assets/cp/CpAsset.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ private function _registerTranslations(View $view): void
323323
'Password',
324324
'Past year',
325325
'Past {num} days',
326+
'Paste {type} above',
327+
'Paste {type} before',
326328
'Paste {type}',
327329
'Pay {price}',
328330
'Pending',

src/web/assets/cp/dist/cp.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/cp/dist/cp.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/cp/src/js/NestedElementManager.js

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,7 @@ Craft.NestedElementManager = Garnish.Base.extend(
675675
}
676676
},
677677

678-
async pasteElements() {
678+
async pasteElements($before = null) {
679679
Craft.cp.announce(Craft.t('app', 'Loading'));
680680
this.$pasteBtn.addClass('loading');
681681

@@ -698,7 +698,7 @@ Craft.NestedElementManager = Garnish.Base.extend(
698698
}
699699

700700
if (this.settings.mode === 'cards') {
701-
const $cards = await this.addElementCards(newElementInfo, false);
701+
const $cards = await this.addElementCards(newElementInfo, $before);
702702
await this.updateSortOrder(newElementInfo[0].id);
703703
Garnish.firstFocusableElement($cards).focus();
704704
} else {
@@ -761,6 +761,7 @@ Craft.NestedElementManager = Garnish.Base.extend(
761761
moveDownButton,
762762
duplicateButton,
763763
copyButton,
764+
pasteButton,
764765
deleteButton;
765766

766767
const $li = $element.parent();
@@ -874,6 +875,27 @@ Craft.NestedElementManager = Garnish.Base.extend(
874875
ul
875876
);
876877
}
878+
879+
// Paste
880+
pasteButton = actionDisclosure.addItem(
881+
{
882+
icon: async () => await Craft.ui.icon('duplicate'),
883+
iconColor: 'fuchsia',
884+
label: this.settings.showInGrid
885+
? Craft.t('app', 'Paste {type} before', {
886+
type: Craft.elementTypeNames[this.elementType][3],
887+
})
888+
: Craft.t('app', 'Paste {type} above', {
889+
type: Craft.elementTypeNames[this.elementType][3],
890+
}),
891+
onActivate: async () => {
892+
if (this.canPaste(Craft.cp.getCopiedElements())) {
893+
await this.pasteElements($element.parent());
894+
}
895+
},
896+
},
897+
ul
898+
);
877899
}
878900

879901
if (Garnish.hasAttr($element, 'data-deletable')) {
@@ -938,6 +960,30 @@ Craft.NestedElementManager = Garnish.Base.extend(
938960
);
939961
}
940962

963+
const copiedElements = Craft.cp.getCopiedElements();
964+
const showPasteButton =
965+
copiedElements.length && this.canPaste(copiedElements);
966+
actionDisclosure.toggleItem(pasteButton, showPasteButton);
967+
if (showPasteButton) {
968+
$(pasteButton)
969+
.children('.menu-item-label')
970+
.text(
971+
this.settings.showInGrid
972+
? Craft.t('app', 'Paste {type} before', {
973+
type:
974+
copiedElements.length === 1
975+
? Craft.elementTypeNames[this.elementType][2]
976+
: Craft.elementTypeNames[this.elementType][3],
977+
})
978+
: Craft.t('app', 'Paste {type} above', {
979+
type:
980+
copiedElements.length === 1
981+
? Craft.elementTypeNames[this.elementType][2]
982+
: Craft.elementTypeNames[this.elementType][3],
983+
})
984+
);
985+
}
986+
941987
if (deleteButton) {
942988
$(deleteButton)
943989
.children('.menu-item-label')
@@ -1060,7 +1106,7 @@ Craft.NestedElementManager = Garnish.Base.extend(
10601106
return await this.addElementCards([element]);
10611107
},
10621108

1063-
async addElementCards(elements) {
1109+
async addElementCards(elements, $before = null) {
10641110
if (this.creatingElement) {
10651111
return null;
10661112
}
@@ -1106,7 +1152,12 @@ Craft.NestedElementManager = Garnish.Base.extend(
11061152

11071153
for (const elementInfo of elements) {
11081154
for (const card of data.elements[elementInfo.id] || []) {
1109-
const $li = $('<li/>').appendTo(this.$elements);
1155+
const $li = $('<li/>');
1156+
if ($before?.length) {
1157+
$li.insertBefore($before);
1158+
} else {
1159+
$li.appendTo(this.$elements);
1160+
}
11101161
const $card = $(card).appendTo($li);
11111162
$cards = $cards.add($card);
11121163
this.initElement($card);

src/web/assets/matrix/dist/MatrixInput.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/matrix/dist/MatrixInput.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/matrix/src/MatrixInput.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@
213213
return true;
214214
},
215215

216-
async pasteEntries() {
216+
async pasteEntries($before = null) {
217217
Craft.cp.announce(Craft.t('app', 'Loading'));
218218
this.$pasteBtn.addClass('loading');
219219

@@ -261,7 +261,13 @@
261261
await this.elementEditor?.pause();
262262

263263
const $newEntries = $(data.blockHtml);
264-
this.$entriesContainer.append($newEntries);
264+
265+
if ($before) {
266+
$newEntries.insertBefore($before);
267+
} else {
268+
this.$entriesContainer.append($newEntries);
269+
}
270+
265271
await Craft.appendHeadHtml(data.headHtml);
266272
await Craft.appendBodyHtml(data.bodyHtml);
267273
Craft.initUiElements($newEntries);
@@ -761,6 +767,25 @@
761767
})
762768
: Craft.t('app', 'Delete')
763769
);
770+
771+
const $pasteBtn = $buttons.filter('[data-action="paste"]');
772+
const copiedElements = Craft.cp.getCopiedElements();
773+
const showPasteButton =
774+
copiedElements.length && this.matrix.canPaste(copiedElements);
775+
if (showPasteButton) {
776+
this.actionDisclosure.showItem($pasteBtn[0]);
777+
$pasteBtn.children('.menu-item-label').text(
778+
copiedElements.length === 1
779+
? Craft.t('app', 'Paste {type} above', {
780+
type: Craft.t('app', 'block'),
781+
})
782+
: Craft.t('app', 'Paste {type} above', {
783+
type: Craft.t('app', 'blocks'),
784+
})
785+
);
786+
} else {
787+
this.actionDisclosure.hideItem($pasteBtn[0]);
788+
}
764789
});
765790

766791
this.actionDisclosure.on('hide', () => {
@@ -1169,6 +1194,10 @@
11691194
break;
11701195
}
11711196

1197+
case 'paste':
1198+
this.matrix.pasteEntries(this.$container);
1199+
break;
1200+
11721201
case 'delete': {
11731202
if (this.bulkActionMode()) {
11741203
if (

0 commit comments

Comments
 (0)