Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
- “Verification Code” and “Recovery Code” forms no longer get auto-submitted when entering a value.

### Administration
- It’s now possible to replace the selected custom field for existing field layout elements. ([#18814](https://github.com/craftcms/cms/pull/18814))
- Sections now have a “Min Authors” setting. ([#18662](https://github.com/craftcms/cms/pull/18662))
- Time fields’ “Max Time” settings can now be set to an earlier time than “Min Time”, for overnight time ranges. ([#18575](https://github.com/craftcms/cms/pull/18575))
- Component chips within component select inputs now have “Replace” actions.
Expand Down Expand Up @@ -62,7 +63,9 @@
- Added `craft\elements\deletionblockers\DeletionBlockerInterface`. ([#18728](https://github.com/craftcms/cms/pull/18728))
- Added `craft\elements\deletionblockers\EntryAuthorsBlocker`. ([#18728](https://github.com/craftcms/cms/pull/18728))
- Added `craft\elements\deletionblockers\RelationDeletionBlocker`. ([#18728](https://github.com/craftcms/cms/pull/18728))
- Added `craft\errors\FieldNotFoundException::$fieldId`.
- Added `craft\events\DefineElementDeletionBlockersEvent`. ([#18728](https://github.com/craftcms/cms/pull/18728))
- Added `craft\fieldlayoutelements\CustomField::setFieldId()`.
- Added `craft\helpers\Html::jsWithVars()`.
- Added `craft\helpers\Markdown`. ([#18671](https://github.com/craftcms/cms/issues/18671))
- Added `craft\models\Section::$minAuthors`. ([#18662](https://github.com/craftcms/cms/pull/18662))
Expand Down
10 changes: 10 additions & 0 deletions src/controllers/FieldsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,16 @@ private function _fldComponent(?array &$settings = null): FieldLayoutComponent
foreach ($tabConfig['elements'] as &$elementConfig) {
if (isset($elementConfig['uid']) && $elementConfig['uid'] === $uid) {
$elementConfig = array_merge($elementConfig, $componentConfig);

// If fieldId is set, we're replacing the selected field
if ($elementConfig['type'] === CustomField::class && isset($elementConfig['fieldId'])) {
if (!empty($elementConfig['fieldId'])) {
unset($elementConfig['fieldUid']);
} else {
unset($elementConfig['fieldId']);
}
}

break 2;
}
}
Expand Down
19 changes: 14 additions & 5 deletions src/errors/FieldNotFoundException.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,22 @@
class FieldNotFoundException extends Exception
{
/**
* @var string The field’s UUID
* @var int|string The field’s ID or UUID
* @since 5.10.0
*/
public string $fieldUid;
public int|string $fieldId;

/**
* Constructor
*
* @param string $fieldUid
* @param int|string $fieldId
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct(string $fieldUid, string $message = '', int $code = 0, ?Throwable $previous = null)
public function __construct(int|string $fieldId, string $message = '', int $code = 0, ?Throwable $previous = null)
{
$this->fieldUid = $fieldUid;
$this->fieldId = $fieldId;
parent::__construct($message, $code, $previous);
}

Expand All @@ -44,4 +45,12 @@ public function getName(): string
{
return 'Field not found';
}

/**
* @deprecated in 5.10.0
*/
public function getFieldUid(): string
{
return (string)$this->fieldId;
}
}
15 changes: 15 additions & 0 deletions src/fieldlayoutelements/CustomField.php
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,21 @@ public function setFieldUid(string $uid): void
$this->_field = null;
}

/**
* Sets the ID of the field this layout field is based on.
*
* @param int $id
* @since 5.10.0
*/
public function setFieldId(int $id): void
{
$field = Craft::$app->getFields()->getFieldById($id);
if (!$field) {
throw new FieldNotFoundException($id);
}
$this->setField($field);
}

/**
* Returns the field’s original handle.
*
Expand Down
3 changes: 3 additions & 0 deletions src/helpers/Cp.php
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,9 @@ public static function chipHtml(Chippable $component, array $config = []): strin
'data' => array_filter([
'type' => get_class($component),
'id' => $component->getId(),
'label' => $component->getUiLabel(),
'description' => $component instanceof Describable ? $component->getDescription() : null,
'handle' => $component instanceof Grippable ? $component->getHandle() : null,
'settings' => $config['autoReload'] ? [
'selectable' => $config['selectable'],
'id' => Craft::$app->getView()->namespaceInputId($config['id']),
Expand Down
11 changes: 11 additions & 0 deletions src/templates/_includes/forms.twig
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@
{% endmacro %}


{% macro fieldSelect(config) %}
{% include "_includes/forms/fieldSelect" with config only %}
{% endmacro %}


{% macro autosuggest(config) %}
{% include "_includes/forms/autosuggest" with config only %}
{% endmacro %}
Expand Down Expand Up @@ -392,6 +397,12 @@
{% endmacro %}


{% macro fieldSelectField(config) %}
{% set config = config|merge({id: config.id ?? "fieldselect#{random()}"}) %}
{{ _self.field(config, 'template:_includes/forms/fieldSelect') }}
{% endmacro %}


{% macro autosuggestField(config) %}
{% set config = config|merge({
id: config.id ?? "autosuggest#{random()}",
Expand Down
7 changes: 7 additions & 0 deletions src/templates/_includes/forms/fieldSelect.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% set id = id ?? "fieldselect#{random()}" -%}

{% include '_includes/forms/componentSelect.twig' with {
options: options ?? craft.app.fields.getAllFields(),
showHandles: true,
createAction: (create ?? false) ? 'fields/edit-field' : null,
} %}
58 changes: 48 additions & 10 deletions src/templates/_includes/forms/fld/custom-field-settings.twig
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,60 @@

{% block fieldSettings %}
{% if originalField %}
{% set fieldChipId = "chip#{random()}" %}
{% set fieldSelectId = "fieldselect#{random()}" %}
<div class="pane no-border p-m">
{{ chip(originalField, {
id: fieldChipId,
class: 'hairline',
showActionMenu: true,
showHandle: true,
hyperlink: false,
{{ forms.fieldSelectField({
id: fieldSelectId,
name: 'fieldId',
value: originalField,
limit: 1,
warning: 'Changing this may result in data loss.'|t('app'),
}) }}
</div>

{% js %}
(() => {
const $chip = $('#{{ fieldChipId|namespaceInputId }}');
$chip.on('dblclick taphold', (ev) => {
$chip.find('.btn').data('disclosureMenu').$container.find('[data-edit-action]').click();
const componentSelect = $("#{{ fieldSelectId|namespaceInputId }}").data('componentSelect');
let replaced = false;

const oldName = {{ defaultLabel|json_encode|raw }};
const oldHandle = {{ defaultHandle|json_encode|raw }};
const oldInstructions = {{ defaultInstructions|json_encode|raw }};

const $labelInput = $("#{{ 'label'|namespaceInputId }}");
const $handleInput = $("#{{ 'handle'|namespaceInputId }}");
const $instructionsInput = $("#{{ 'instructions'|namespaceInputId }}");

componentSelect.on('change', () => {
const $chip = componentSelect.getComponents().first();

if (!$chip.length) {
return;
}

const newName = $chip.data('label');
const newHandle = $chip.data('handle');
const newInstructions = $chip.data('instructions');

$labelInput.attr('placeholder', newName);
$handleInput.attr('placeholder', newHandle);
$instructionsInput.attr('placeholder', newInstructions);

if (!replaced) {
// Override the name/handle/instructions for consistency with the old field,
// if they weren't already being overridden
if (!$labelInput.val()) {
$labelInput.val(oldName);
}
if (!$handleInput.val()) {
$handleInput.val(oldHandle);
}
if (!$instructionsInput.val()) {
$instructionsInput.val(oldInstructions);
}
}

replaced = true;
});
})();
{% endjs %}
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.

43 changes: 26 additions & 17 deletions src/web/assets/cp/src/js/ComponentSelectInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,9 @@ Craft.ComponentSelectInput = Garnish.Base.extend(
icon: async () => await Craft.ui.icon('arrows-rotate'),
label: Craft.t('app', 'Replace'),
callback: () => {
this.removeComponent($component, () => {
this.$addBtn.disclosureMenu().data('disclosureMenu').show();
});
const menu = this.$addBtn.disclosureMenu().data('disclosureMenu');
menu.$alignmentElement = $component;
menu.show();
},
});
}
Expand Down Expand Up @@ -462,16 +462,28 @@ Craft.ComponentSelectInput = Garnish.Base.extend(
this.onChange();
},

removeComponent: function ($component, callback = null) {
removeComponent: function ($component, animate = true, callback = null) {
if (typeof animate == 'function') {
callback = animate;
animate = true;
}

// Remove any inputs from the form data
$('[name]', $component).removeAttr('name');
this.removeComponents($component);
this.animateComponentAway($component, () => {

const afterRemove = () => {
$component.parent('li').remove();
if (callback) {
callback();
}
});
};

if (animate) {
this.animateComponentAway($component, afterRemove);
} else {
afterRemove();
}
},

animateComponentAway: function ($component, callback) {
Expand Down Expand Up @@ -531,22 +543,19 @@ Craft.ComponentSelectInput = Garnish.Base.extend(
}
);

const canAdd = this.canAddMoreComponents();
let $item = false;

if (canAdd) {
const $component = $(data.components[type][id][0]);
this.insertComponent($component);
this.addComponents($component);
$item = $component;
if (!this.canAddMoreComponents()) {
this.removeComponent(this.getComponents().last(), false);
}

const $component = $(data.components[type][id][0]);
this.insertComponent($component);
this.addComponents($component);
let $item = $component;

if (addToMenu && disclosureMenu) {
const $menuItem = $(data.menuItems[type][id]);
disclosureMenu.addItem($menuItem);
if (canAdd) {
disclosureMenu.hideItem($menuItem.children()[0]);
}
disclosureMenu.hideItem($menuItem.children()[0]);
$item = $menuItem;
$menuItem.find('button').on('activate', () => {
this.addComponent(type, id);
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.

17 changes: 11 additions & 6 deletions src/web/assets/garnish/src/DisclosureMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,7 @@ export default Base.extend(
}

// Capture additional alignment element
const alignmentSelector = this.$container.data('align-to');
if (alignmentSelector) {
this.$alignmentElement = this.$trigger.find(alignmentSelector).first();
} else {
this.$alignmentElement = this.$trigger;
}
this.captureAlignmentElement();

this.$container.appendTo(Garnish.$bod);
// if trigger is in a slideout, we need to initialise UI elements
Expand All @@ -103,6 +98,15 @@ export default Base.extend(
Garnish.DisclosureMenu.instances.push(this);
},

captureAlignmentElement: function () {
const alignmentSelector = this.$container.data('align-to');
if (alignmentSelector) {
this.$alignmentElement = this.$trigger.find(alignmentSelector).first();
} else {
this.$alignmentElement = this.$trigger;
}
},

addSearchInput: function () {
const $outerContainer = $('<div/>', {
class: 'search-container',
Expand Down Expand Up @@ -371,6 +375,7 @@ export default Base.extend(

handleTriggerClick: function () {
if (!this.isExpanded()) {
this.captureAlignmentElement();
this.show();
} else {
this.hide();
Expand Down