Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show provisional draft data in element chips/cards/tables #14975

3 changes: 3 additions & 0 deletions src/base/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,9 @@ public static function indexHtml(
]);
}

// See if there are any provisional drafts we should swap these out with
ElementHelper::swapInProvisionalDrafts($elements);

$variables['elements'] = $elements;
$template = '_elements/' . $viewState['mode'] . 'view/' . ($includeContainer ? 'container' : 'elements');

Expand Down
8 changes: 6 additions & 2 deletions src/controllers/AppController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use craft\helpers\ArrayHelper;
use craft\helpers\Cp;
use craft\helpers\DateTimeHelper;
use craft\helpers\ElementHelper;
use craft\helpers\Html;
use craft\helpers\Json;
use craft\helpers\Search;
Expand Down Expand Up @@ -762,17 +763,20 @@ public function actionRenderElements(): Response
->id($id)
->fixedOrder()
->drafts(null)
->provisionalDrafts(null)
->revisions(null)
->siteId($siteId)
->status(null)
->all();

// See if there are any provisional drafts we should swap these out with
ElementHelper::swapInProvisionalDrafts($elements);

foreach ($elements as $element) {
foreach ($instances as $key => $instance) {
$id = $element->isProvisionalDraft ? $element->getCanonicalId() : $element->id;
/** @var 'chip'|'card' $ui */
$ui = $instance['ui'] ?? 'chip';
$elementHtml[$element->id][$key] = match ($ui) {
$elementHtml[$id][$key] = match ($ui) {
'chip' => Cp::elementChipHtml($element, $instance),
'card' => Cp::elementCardHtml($element, $instance),
};
Expand Down
19 changes: 15 additions & 4 deletions src/controllers/ElementIndexesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1041,16 +1041,27 @@ public function actionElementTableHtml(): Response
throw new ForbiddenHttpException('User not authorized to edit content for this site.');
}

// check for a provisional draft first
/** @var ElementInterface|null $element */
$element = $elementType::find()
->id($id)
->drafts(null)
->provisionalDrafts(null)
->revisions(null)
->draftOf($id)
->provisionalDrafts()
->siteId($siteId)
->status(null)
->one();

if (!$element) {
/** @var ElementInterface|null $element */
$element = $elementType::find()
->id($id)
->drafts(null)
->provisionalDrafts(null)
->revisions(null)
->siteId($siteId)
->status(null)
->one();
}

if (!$element) {
throw new BadRequestHttpException("Invalid element ID: $id");
}
Expand Down
3 changes: 3 additions & 0 deletions src/elements/NestedElementManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,9 @@ function(string $id, array $config, $attribute, &$settings) use ($owner) {
->all();
}

// See if there are any provisional drafts we should swap these out with
ElementHelper::swapInProvisionalDrafts($elements);

$this->setOwnerOnNestedElements($owner, $elements);

if (!empty($elements)) {
Expand Down
22 changes: 20 additions & 2 deletions src/elements/db/ElementQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ class ElementQuery extends Query implements ElementQueryInterface
* This can be set to one of the following:
*
* - A source element ID – matches drafts of that element
* - A source element
* - An array of source elements or element IDs
* - `'*'` – matches drafts of any source element
* - `false` – matches unpublished drafts that have no source element
*
Expand Down Expand Up @@ -701,11 +703,27 @@ public function draftId(?int $value = null): static
*/
public function draftOf($value): static
{
$valid = false;
if ($value instanceof ElementInterface) {
$this->draftOf = $value->getCanonicalId();
} elseif (is_numeric($value) || $value === '*' || $value === false || $value === null) {
$valid = true;
} elseif (
is_numeric($value) ||
(is_array($value) && ArrayHelper::isNumeric($value)) ||
$value === '*' ||
$value === false ||
$value === null
) {
$this->draftOf = $value;
} else {
$valid = true;
} elseif (is_array($value) && !empty($value)) {
$c = Collection::make($value);
if ($c->every(fn($v) => $v instanceof ElementInterface || is_numeric($v))) {
$this->draftOf = $c->map(fn($v) => $v instanceof ElementInterface ? $v->id : $v)->all();
$valid = true;
}
}
if (!$valid) {
throw new InvalidArgumentException('Invalid draftOf value');
}
if ($value !== null && $this->drafts === false) {
Expand Down
2 changes: 2 additions & 0 deletions src/elements/db/ElementQueryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,9 @@ public function draftId(?int $value = null): static;
* | Value | Fetches drafts…
* | - | -
* | `1` | for the {element} with an ID of 1.
* | `[1, 2]` | for the {elements} with an ID of 1 or 2.
* | a [[{element-class}]] object | for the {element} represented by the object.
* | an array of [[{element-class}]] objects | for the {elements} represented by the objects.
* | `'*'` | for any {element}
* | `false` | that aren’t associated with a published {element}
*
Expand Down
1 change: 1 addition & 0 deletions src/fields/BaseRelationField.php
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,7 @@ protected function inputTemplateVariables(array|ElementQueryInterface $value = n
{
if ($value instanceof ElementQueryInterface) {
$value = $value->all();
ElementHelper::swapInProvisionalDrafts($value);
} elseif (!is_array($value)) {
$value = [];
}
Expand Down
26 changes: 23 additions & 3 deletions src/helpers/Cp.php
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,10 @@ public static function elementChipHtml(ElementInterface $element, array $config
$config['attributes'],
fn() => $element->getChipLabelHtml(),
);

if ($element->isProvisionalDraft) {
$config['labelHtml'] .= self::changeStatusLabelHtml();
}
}

$html = static::chipHtml($element, $config);
Expand Down Expand Up @@ -560,9 +564,14 @@ public static function elementCardHtml(ElementInterface $element, array $config
$headingContent = self::elementLabelHtml($element, $config, $attributes, fn() => Html::encode($element->getUiLabel()));
$bodyContent = $element->getCardBodyHtml() ?? '';

$statusLabel = $element::hasStatuses() ? static::componentStatusLabelHtml($element) : null;
if ($statusLabel) {
$bodyContent .= Html::tag('div', $statusLabel, ['class' => 'flex']);
$labels =
($element::hasStatuses() ? static::componentStatusLabelHtml($element) : '') .
($element->isProvisionalDraft ? self::changeStatusLabelHtml() : '');

if ($labels !== '') {
$bodyContent .= Html::tag('div', $labels, [
'class' => ['flex', 'gap-xs'],
]);
}

$thumb = $element->getThumbHtml(128);
Expand Down Expand Up @@ -743,6 +752,15 @@ public static function statusLabelHtml(array $config = []): ?string
]);
}

private static function changeStatusLabelHtml(): string
{
return static::statusLabelHtml([
'color' => Color::Blue,
'icon' => 'pen-circle',
'label' => Craft::t('app', 'Edited'),
]);
}

/**
* Renders status label HTML for a [[Statusable]] component.
*
Expand Down Expand Up @@ -793,9 +811,11 @@ private static function baseElementAttributes(ElementInterface $element, array $
'data' => array_filter([
'type' => get_class($element),
'id' => $element->id,
'canonical-id' => $element->getCanonicalId(),
'draft-id' => $element->draftId,
'revision-id' => $element->revisionId,
'site-id' => $element->siteId,
'provisional' => $element->isProvisionalDraft,
'status' => $element->getStatus(),
'label' => (string)$element,
'url' => $element->getUrl(),
Expand Down
40 changes: 40 additions & 0 deletions src/helpers/ElementHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -916,4 +916,44 @@ public static function renderElements(array $elements, array $variables = []): M

return new Markup(implode("\n", $output), Craft::$app->charset);
}

/**
* Swaps out any canonical elements with provisional drafts, when they exist.
*
* @param ElementInterface[] $elements
* @since 5.2.0
*/
public static function swapInProvisionalDrafts(array &$elements): void
{
$canonicalElements = array_filter($elements, fn(ElementInterface $element) => $element->getIsCanonical());

if (empty($canonicalElements)) {
return;
}

$first = reset($canonicalElements);

if (!$first::hasDrafts()) {
return;
}

$drafts = $first::find()
->draftOf($canonicalElements)
->provisionalDrafts()
->siteId($first->siteId)
->status(null)
->indexBy('canonicalId')
->all();

if (empty($drafts)) {
return;
}

// array_filter() preserves keys, so it's safe to loop through it rather than $elements here
foreach ($canonicalElements as $i => $element) {
if (isset($drafts[$element->id])) {
$elements[$i] = $drafts[$element->id];
}
}
}
}
1 change: 1 addition & 0 deletions src/icons/solid/pen-circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 13 additions & 2 deletions src/templates/_elements/tableview/elements.twig
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,18 @@
: 0 %}
{% set elementTitle = element.title ?: element.id %}
{% set showInputs = (inlineEditing ?? false) and elementsService.canSave(element, currentUser) %}
<tr data-id="{{ element.id }}"{% if structure %} data-title="{{ elementTitle }}" data-level="{{ element.level }}" data-descendants="{{ totalDescendants ?? 0 }}"{% endif %}{% if element.id in disabledElementIds %} class="disabled"{% endif %}>
{% tag 'tr' with {
data: {
id: element.id,
'canonical-id': element.getCanonicalId(),
title: elementTitle,
level: structure ? element.level : false,
descendants: structure ? (totalDescendants ?? 0) : false,
},
class: {
disabled: element.id in disabledElementIds,
}|filter|keys,
} %}
{% if selectable %}
<td class="checkbox-cell">
{{ tag('div', {
Expand Down Expand Up @@ -95,7 +106,7 @@
</td>
{% endif %}
{% endfor %}
</tr>
{% endtag %}
{% endfor %}

{% endapply -%}
1 change: 1 addition & 0 deletions src/translations/en/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -1669,6 +1669,7 @@
'This year' => 'This year',
'This {type} doesn’t have revisions.' => 'This {type} doesn’t have revisions.',
'This {type} has been updated.' => 'This {type} has been updated.',
'This {type} has unsaved changes.' => 'This {type} has unsaved changes.',
'Time Zone' => 'Time Zone',
'Time fields are better suited for managing Time-only values.' => 'Time fields are better suited for managing Time-only values.',
'Time to reserve' => 'Time to reserve',
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.

4 changes: 3 additions & 1 deletion src/web/assets/cp/src/js/Craft.js
Original file line number Diff line number Diff line change
Expand Up @@ -2371,7 +2371,9 @@ $.extend(Craft, {
},

refreshElementInstances(elementId) {
const $elements = $(`div.element[data-id="${elementId}"][data-settings]`);
const $elements = $(
`div.element[data-id="${elementId}"][data-settings],div.element[data-canonical-id="${elementId}"][data-provisional][data-settings]`
);
if (!$elements.length) {
return;
}
Expand Down
17 changes: 17 additions & 0 deletions src/web/assets/cp/src/js/ElementEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,14 @@ Craft.ElementEditor = Garnish.Base.extend(
})
.then((response) => {
Craft.cp.displaySuccess(response.data.message);

// Broadcast a saveMessage event, in case any chips/cards should be
// updated to stop showing the provisional changes
Craft.broadcaster.postMessage({
event: 'saveElement',
id: this.settings.canonicalId,
});

this.slideout.close();
})
.catch(reject);
Expand Down Expand Up @@ -1822,6 +1830,15 @@ Craft.ElementEditor = Garnish.Base.extend(
}

this.trigger('update');

if (this.settings.isProvisionalDraft && Craft.broadcaster) {
// Broadcast a saveMessage event, in case any chips/cards should be
// updated to show the provisional changes
Craft.broadcaster.postMessage({
event: 'saveElement',
id: this.settings.canonicalId,
});
}
},

setStatusMessage: function (message) {
Expand Down
14 changes: 11 additions & 3 deletions src/web/assets/cp/src/js/ElementEditorSlideout.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,21 @@ Craft.ElementEditorSlideout = Craft.CpScreenSlideout.extend(

if (this.settings.elementId) {
params.elementId = this.settings.elementId;
} else if (this.$element && this.$element.data('id')) {
params.elementId = this.$element.data('id');
} else if (this.$element) {
if (this.$element.data('canonical-id')) {
params.elementId = this.$element.data('canonical-id');
} else if (this.$element.data('id')) {
params.elementId = this.$element.data('id');
}
}

if (this.settings.draftId) {
params.draftId = this.settings.draftId;
} else if (this.$element && this.$element.data('draft-id')) {
} else if (
this.$element &&
this.$element.data('draft-id') &&
!Garnish.hasAttr(this.$element, 'data-provisional')
) {
params.draftId = this.$element.data('draft-id');
} else if (this.settings.revisionId) {
params.revisionId = this.settings.revisionId;
Expand Down
2 changes: 1 addition & 1 deletion src/web/assets/cp/src/js/TableElementIndexView.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Craft.TableElementIndexView = Craft.BaseElementIndexView.extend({
this._broadcastListener = (ev) => {
if (ev.data.event === 'saveElement') {
const $rows = this.$table.find(
`> tbody > tr[data-id="${ev.data.id}"]`
`> tbody > tr[data-id="${ev.data.id}"],> tbody > tr[data-canonical-id="${ev.data.id}"]`
);
if ($rows.length) {
const data = {
Expand Down
Loading