diff --git a/formwork/fields/file.php b/formwork/fields/file.php new file mode 100644 index 000000000..82e070075 --- /dev/null +++ b/formwork/fields/file.php @@ -0,0 +1,61 @@ + function (Field $field): ?File { + return $field->value() !== null + ? $field->getFiles()->get($field->value()) + : null; + }, + + 'getFiles' => function (Field $field): FileCollection { + if (!$field->has('options')) { + $model = $field->parent()?->model(); + + if ($model === null || !method_exists($model, 'files')) { + throw new InvalidValueException(sprintf('Field "%s" of type "%s" must have a model with files', $field->name(), $field->type())); + } + + return $model->files(); + } + + return $field->get('options'); + }, + + 'validate' => function (Field $field, $value): ?string { + if (Constraint::isEmpty($value)) { + return null; + } + + if (!is_string($value)) { + throw new ValidationException(sprintf('Invalid value for field "%s" of type "%s"', $field->name(), $field->type())); + } + + return $value; + }, + + 'options' => function (Field $field): array { + $collection = $field->getFiles(); + + if ($field->has('fileType')) { + $collection = $collection->filter(static fn(File $file) => in_array($file->type(), (array) $field->get('fileType'), true)); + } + + return $collection + ->map(static fn(File $file) => [ + 'value' => $file->name(), + 'icon' => 'file-' . $file->type(), + 'thumb' => $file instanceof Image ? $file->square(300, 'contain')->uri() : null, + ])->toArray(); + }, + ]; +}; diff --git a/formwork/fields/files.php b/formwork/fields/files.php new file mode 100644 index 000000000..4be82e2cf --- /dev/null +++ b/formwork/fields/files.php @@ -0,0 +1,83 @@ + function (Field $field): FileCollection { + if (!$field->has('options')) { + $model = $field->parent()?->model(); + + if ($model === null || !method_exists($model, 'files')) { + throw new InvalidValueException(sprintf('Field "%s" of type "%s" must have a model with files', $field->name(), $field->type())); + } + + return $model->files(); + } + + return $field->get('options'); + }, + + 'toString' => function ($field) { + return implode(', ', $field->value() ?? []); + }, + + 'return' => function (Field $field): FileCollection { + return $field->getFiles()->filter(static fn(File $file) => in_array($file->name(), $field->value(), true)); + }, + + 'validate' => function (Field $field, $value): array { + if (Constraint::isEmpty($value)) { + return []; + } + + if (is_string($value)) { + $value = array_map(trim(...), explode(',', $value)); + } + + if (!is_array($value)) { + throw new ValidationException(sprintf('Invalid value for field "%s" of type "%s"', $field->name(), $field->type())); + } + + if ($field->has('pattern')) { + $value = array_filter($value, static fn($item): bool => Constraint::matchesRegex($item, $field->get('pattern'))); + } + + if ($field->limit() !== null && count($value) > $field->limit()) { + throw new ValidationException(sprintf('Field "%s" of type "%s" has a limit of %d items', $field->name(), $field->type(), $field->get('limit'))); + } + + return array_values(array_filter($value)); + }, + + 'options' => function (Field $field): array { + $collection = $field->getFiles(); + + if ($field->has('fileType')) { + $collection = $collection->filter(static fn(File $file) => in_array($file->type(), (array) $field->get('fileType'), true)); + } + + return $collection + ->map(static fn(File $file) => [ + 'value' => $file->name(), + 'icon' => 'file-' . $file->type(), + 'thumb' => $file instanceof Image ? $file->square(300, 'contain')->uri() : null, + ])->toArray(); + }, + + 'limit' => function (Field $field): ?int { + return $field->get('limit', null); + }, + + 'isOrderable' => function ($field): bool { + return $field->is('orderable', true); + }, + ]; +}; diff --git a/formwork/fields/image.php b/formwork/fields/image.php index 440a31299..40a340159 100644 --- a/formwork/fields/image.php +++ b/formwork/fields/image.php @@ -1,14 +1,36 @@ function (Field $field): Field { - return $field; + 'return' => function (Field $field): ?Image { + return $field->value() !== null + ? $field->getImages()->get($field->value()) + : null; + }, + + 'getImages' => function (Field $field): FileCollection { + if (!$field->has('options')) { + $model = $field->parent()?->model(); + + if ($model === null || !method_exists($model, 'files')) { + throw new InvalidValueException(sprintf('Field "%s" of type "%s" must have a model with files', $field->name(), $field->type())); + } + + $files = $model->files(); + } else { + $files = $field->get('options'); + } + + return $files->filter(static fn(File $file) => $file instanceof Image); }, 'validate' => function (Field $field, $value): ?string { @@ -22,5 +44,14 @@ return $value; }, + + 'options' => function (Field $field): array { + return $field->getImages() + ->map(static fn(Image $image) => [ + 'value' => $image->name(), + 'icon' => 'image', + 'thumb' => $image->square(300, 'contain')->uri(), + ])->toArray(); + }, ]; }; diff --git a/formwork/fields/images.php b/formwork/fields/images.php new file mode 100644 index 000000000..a15179527 --- /dev/null +++ b/formwork/fields/images.php @@ -0,0 +1,79 @@ + function (Field $field): FileCollection { + if (!$field->has('options')) { + $model = $field->parent()?->model(); + + if ($model === null || !method_exists($model, 'files')) { + throw new InvalidValueException(sprintf('Field "%s" of type "%s" must have a model with files', $field->name(), $field->type())); + } + + $files = $model->files(); + } else { + $files = $field->get('options'); + } + + return $files->filter(static fn(File $file) => $file instanceof Image); + }, + + 'toString' => function ($field) { + return implode(', ', $field->value() ?? []); + }, + + 'return' => function (Field $field): FileCollection { + return $field->getImages()->filter(static fn(File $file) => in_array($file->name(), $field->value(), true)); + }, + + 'validate' => function (Field $field, $value): array { + if (Constraint::isEmpty($value)) { + return []; + } + + if (is_string($value)) { + $value = array_map(trim(...), explode(',', $value)); + } + + if (!is_array($value)) { + throw new ValidationException(sprintf('Invalid value for field "%s" of type "%s"', $field->name(), $field->type())); + } + + if ($field->has('pattern')) { + $value = array_filter($value, static fn($item): bool => Constraint::matchesRegex($item, $field->get('pattern'))); + } + + if ($field->limit() !== null && count($value) > $field->limit()) { + throw new ValidationException(sprintf('Field "%s" of type "%s" has a limit of %d items', $field->name(), $field->type(), $field->get('limit'))); + } + + return array_values(array_filter($value)); + }, + + 'options' => function (Field $field): array { + return $field->getImages() + ->map(static fn(Image $image) => [ + 'value' => $image->name(), + 'icon' => 'image', + 'thumb' => $image->square(300, 'contain')->uri(), + ])->toArray(); + }, + + 'limit' => function (Field $field): ?int { + return $field->get('limit', null); + }, + + 'isOrderable' => function ($field): bool { + return $field->is('orderable', true); + }, + ]; +}; diff --git a/formwork/fields/tags.php b/formwork/fields/tags.php index 7cc21c316..9322fe29e 100644 --- a/formwork/fields/tags.php +++ b/formwork/fields/tags.php @@ -34,7 +34,7 @@ $value = array_filter($value, static fn($item): bool => Constraint::matchesRegex($item, $field->get('pattern'))); } - if ($field->has('limit') && count($value) > $field->get('limit')) { + if ($field->limit() !== null && count($value) > $field->limit()) { throw new ValidationException(sprintf('Field "%s" of type "%s" has a limit of %d items', $field->name(), $field->type(), $field->get('limit'))); } @@ -47,8 +47,16 @@ return $options !== null ? Arr::from($options) : null; }, + 'accept' => function ($field): string { + return $field->get('accept', 'options'); + }, + 'limit' => function ($field): ?int { return $field->get('limit', null); }, + + 'isOrderable' => function ($field): bool { + return $field->is('orderable', true); + }, ]; }; diff --git a/formwork/translations/en.yaml b/formwork/translations/en.yaml index 0b3ce29b9..7096f1f17 100644 --- a/formwork/translations/en.yaml +++ b/formwork/translations/en.yaml @@ -17,7 +17,9 @@ date.weekdays.long: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Fr date.weekdays.short: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] fields.array.add: Add fields.array.remove: Remove +fields.file.none: (None) fields.file.uploadLabel: Click to choose a file to upload or drag it here +fields.image.none: (None) fields.select.empty: No matching options file.metadata: Metadata file.metadata.alternativeText: Alternative text @@ -28,6 +30,7 @@ page.files: Files page.image: Image page.listed: Listed page.listed.description: Listed pages are visible in the menu +page.noFiles: No files page.noImage: No image page.none: (None) page.noTags: No tags diff --git a/panel/src/scss/components/_dropdowns.scss b/panel/src/scss/components/_dropdowns.scss index f5ed026f2..5ae86f478 100644 --- a/panel/src/scss/components/_dropdowns.scss +++ b/panel/src/scss/components/_dropdowns.scss @@ -95,3 +95,19 @@ border-top: 1px solid var(--color-base-500); margin: 0.25rem 0; } + +.dropdown-list .dropdown-item > .dropdown-thumb { + display: inline-block; + width: 1.5rem; + height: 1.5rem; + border-radius: $border-radius; + margin: -0.75rem 0.5rem -0.5rem 0; + background-color: var(--color-base-700); + object-fit: contain; + vertical-align: middle; +} + +.dropdown-list .dropdown-item > .icon { + margin-right: 0.5rem; + margin-left: 0.25rem; +} diff --git a/panel/src/scss/components/_forms.scss b/panel/src/scss/components/_forms.scss index de17eddcc..90bdecd88 100644 --- a/panel/src/scss/components/_forms.scss +++ b/panel/src/scss/components/_forms.scss @@ -8,5 +8,5 @@ @use "forms/forms-file"; @use "forms/forms-image"; @use "forms/forms-range"; -@use "forms/forms-tag"; +@use "forms/forms-tags"; @use "forms/forms-togglegroup"; diff --git a/panel/src/scss/components/forms/_forms-image.scss b/panel/src/scss/components/forms/_forms-image.scss index 0c0a17631..2f552f2b6 100644 --- a/panel/src/scss/components/forms/_forms-image.scss +++ b/panel/src/scss/components/forms/_forms-image.scss @@ -1,10 +1,6 @@ @use "../mixins" as *; @use "../variables" as *; -.form-input-image { - cursor: default; -} - .image-picker-thumbnails { display: flex; overflow: auto; diff --git a/panel/src/scss/components/forms/_forms-tag.scss b/panel/src/scss/components/forms/_forms-tags.scss similarity index 77% rename from panel/src/scss/components/forms/_forms-tag.scss rename to panel/src/scss/components/forms/_forms-tags.scss index fbe9f6312..04632bed1 100644 --- a/panel/src/scss/components/forms/_forms-tag.scss +++ b/panel/src/scss/components/forms/_forms-tags.scss @@ -1,7 +1,7 @@ @use "../mixins" as *; @use "../variables" as *; -.form-input-tag { +.form-input-tags-wrap { position: relative; display: block; box-sizing: border-box; @@ -16,19 +16,19 @@ @include user-select-none; } -.form-input-wrap > .form-input-tag { +.form-input-wrap > .form-input-tags-wrap { margin-bottom: 0; } -.form-input-wrap .form-input-icon + .form-input-tag { - padding-left: 1.5rem; +.form-input-wrap .form-input-icon + .form-input-tags-wrap { + padding-left: 1.75rem; } -.form-input-tag.focused { +.form-input-tags-wrap.focused { border-color: var(--color-accent-500); } -.tag-inner-input { +.form-input-tags-wrap .form-input { display: inline-block; width: auto; max-width: 100%; @@ -46,11 +46,7 @@ } } -.form-input-tag-hidden { - display: none; -} - -.form-input-tag .tag { +.form-input-tags-wrap .tag { display: inline-block; box-sizing: border-box; padding: 0 0.375rem; @@ -65,16 +61,12 @@ } } -.form-input-tag .tag:first-child { - margin-left: 0.25rem; -} - -.form-input-tag.disabled, -.form-input-tag.disabled .tag-inner-input { +.form-input-tags-wrap.disabled, +.form-input-tags-wrap.disabled .form-input { background-color: var(--color-base-800); } -.form-input-tag.disabled .tag { +.form-input-tags-wrap.disabled .tag { background-color: var(--color-base-600); } @@ -96,7 +88,7 @@ font-weight: 600; } -.form-input-tag.is-dragging, -.form-input-tag.is-dragging * { +.form-input-tags-wrap.is-dragging, +.form-input-tags-wrap.is-dragging * { cursor: grabbing !important; } diff --git a/panel/src/ts/components/inputs.ts b/panel/src/ts/components/inputs.ts index 87bbd00eb..131a5f1f4 100644 --- a/panel/src/ts/components/inputs.ts +++ b/panel/src/ts/components/inputs.ts @@ -5,12 +5,11 @@ import { DateInput } from "./inputs/date-input"; import { DurationInput } from "./inputs/duration-input"; import { EditorInput } from "./inputs/editor-input"; import { FileInput } from "./inputs/file-input"; -import { ImageInput } from "./inputs/image-input"; import { ImagePicker } from "./inputs/image-picker"; import { RangeInput } from "./inputs/range-input"; import { SelectInput } from "./inputs/select-input"; import { SlugInput } from "./inputs/slug-input"; -import { TagInput } from "./inputs/tag-input"; +import { TagsInput } from "./inputs/tags-input"; export class Inputs { [name: string]: object; @@ -18,15 +17,13 @@ export class Inputs { constructor(parent: HTMLElement) { $$(".form-input-date", parent).forEach((element: HTMLInputElement) => (this[element.name] = new DateInput(element, app.config.DateInput))); - $$(".form-input-image", parent).forEach((element: HTMLInputElement) => (this[element.name] = new ImageInput(element))); - $$(".image-picker", parent).forEach((element: HTMLSelectElement) => (this[element.name] = new ImagePicker(element))); $$(".editor-textarea", parent).forEach((element: HTMLTextAreaElement) => (this[element.name] = new EditorInput(element))); $$("input[type=file]", parent).forEach((element: HTMLInputElement) => (this[element.name] = new FileInput(element))); - $$("input[data-field=tags]", parent).forEach((element: HTMLInputElement) => (this[element.name] = new TagInput(element, app.config.TagInput))); + $$(".form-input-tags", parent).forEach((element: HTMLInputElement) => (this[element.name] = new TagsInput(element, app.config.TagInput))); $$("input[data-field=duration]", parent).forEach((element: HTMLInputElement) => (this[element.name] = new DurationInput(element, app.config.DurationInput))); diff --git a/panel/src/ts/components/inputs/image-input.ts b/panel/src/ts/components/inputs/image-input.ts deleted file mode 100644 index cd2f1ec8f..000000000 --- a/panel/src/ts/components/inputs/image-input.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { $ } from "../../utils/selectors"; -import { app } from "../../app"; - -export class ImageInput { - constructor(element: HTMLInputElement) { - element.addEventListener("click", () => { - app.modals["imagesModal"].show(undefined, (modal) => { - const selected = $(".image-picker-thumbnail.selected", modal.element); - if (selected) { - selected.classList.remove("selected"); - } - if (element.value) { - const thumbnail = $(`.image-picker-thumbnail[data-filename="${element.value}"]`, modal.element); - if (thumbnail) { - thumbnail.classList.add("selected"); - } - } - const confirm = $("[data-command=pick-image]", modal.element) as HTMLElement; - confirm.dataset.target = element.id; - confirm.addEventListener("click", () => modal.hide()); - }); - }); - } -} diff --git a/panel/src/ts/components/inputs/select-input.ts b/panel/src/ts/components/inputs/select-input.ts index df85710aa..a247bc34e 100644 --- a/panel/src/ts/components/inputs/select-input.ts +++ b/panel/src/ts/components/inputs/select-input.ts @@ -1,5 +1,6 @@ import { $, $$ } from "../../utils/selectors"; import { escapeRegExp, makeDiacriticsRegExp } from "../../utils/validation"; +import { insertIcon } from "../icons"; type SelectInputListItem = { label: string; @@ -120,7 +121,19 @@ export class SelectInput { item.classList.add("disabled"); } + if (option.dataset.thumb) { + const img = document.createElement("img"); + img.src = option.dataset.thumb; + img.className = "dropdown-thumb"; + item.insertAdjacentElement("afterbegin", img); + } else if (option.dataset.icon) { + insertIcon(option.dataset.icon, item); + } + for (const key in option.dataset) { + if (["icon", "thumb"].includes(key)) { + continue; + } item.dataset[key] = option.dataset[key]; } @@ -358,7 +371,7 @@ export class SelectInput { function setCurrent(item: HTMLElement) { select.value = item.dataset.value as string; - labelInput.value = item.innerText; + labelInput.value = item.innerText.trim(); select.dispatchEvent(new Event("input", { bubbles: true })); select.dispatchEvent(new Event("change", { bubbles: true })); } @@ -368,7 +381,7 @@ export class SelectInput { } function getCurrentLabel() { - return getCurrent().innerText; + return getCurrent().innerText.trim(); } function selectCurrent() { diff --git a/panel/src/ts/components/inputs/tag-input.ts b/panel/src/ts/components/inputs/tags-input.ts similarity index 94% rename from panel/src/ts/components/inputs/tag-input.ts rename to panel/src/ts/components/inputs/tags-input.ts index c4afdc29f..ee31fecd6 100644 --- a/panel/src/ts/components/inputs/tag-input.ts +++ b/panel/src/ts/components/inputs/tags-input.ts @@ -1,9 +1,10 @@ import { $, $$ } from "../../utils/selectors"; import { escapeRegExp, makeDiacriticsRegExp } from "../../utils/validation"; import { debounce } from "../../utils/events"; +import { insertIcon } from "../icons"; import Sortable from "sortablejs"; -interface TagInputOptions { +interface TagsInputOptions { labels: { [key: string]: string }; addKeyCodes: string[]; limit: number; @@ -11,8 +12,14 @@ interface TagInputOptions { orderable: boolean; } -export class TagInput { - constructor(input: HTMLInputElement, userOptions: Partial) { +interface TagsInputDropdownItem { + value: string; + icon?: string; + thumb?: string; +} + +export class TagsInput { + constructor(input: HTMLInputElement, userOptions: Partial) { const defaults = { addKeyCodes: ["Comma"], limit: Infinity, accept: "options", orderable: true }; const options = Object.assign({}, defaults, userOptions); @@ -42,15 +49,15 @@ export class TagInput { options.orderable = false; } - field.className = "form-input-tag"; + field.className = "form-input-tags-wrap"; - innerInput.className = "form-input tag-inner-input"; + innerInput.className = "form-input"; innerInput.type = "text"; + innerInput.id = input.id; innerInput.placeholder = input.placeholder; - hiddenInput.className = "form-input-tag-hidden"; + hiddenInput.className = "form-input-hidden"; hiddenInput.name = input.name; - hiddenInput.id = input.id; hiddenInput.type = "text"; hiddenInput.value = input.value; hiddenInput.readOnly = true; @@ -130,7 +137,7 @@ export class TagInput { function createDropdown() { if ("options" in input.dataset) { - const list: { [key: string | number]: string } = JSON.parse(input.dataset.options ?? "{}"); + const list: { [key: string | number]: string | TagsInputDropdownItem } = JSON.parse(input.dataset.options ?? "{}"); const isAssociative = !Array.isArray(list); if ("accept" in input.dataset) { @@ -143,9 +150,22 @@ export class TagInput { for (const key in list) { const item = document.createElement("div"); + + const { value, icon, thumb } = typeof list[key] === "object" ? list[key] : { value: list[key], icon: undefined, thumb: undefined }; + item.className = "dropdown-item"; - item.innerHTML = list[key]; - item.dataset.value = isAssociative ? key : list[key]; + item.innerHTML = value; + item.dataset.value = isAssociative ? key : value; + + if (thumb) { + const img = document.createElement("img"); + img.src = thumb; + img.className = "dropdown-thumb"; + item.insertAdjacentElement("afterbegin", img); + } else if (icon) { + insertIcon(icon, item); + } + item.addEventListener("click", function () { if (this.dataset.value) { addTag(this.dataset.value); diff --git a/panel/views/fields/file.php b/panel/views/fields/file.php new file mode 100644 index 000000000..8a9b57cb5 --- /dev/null +++ b/panel/views/fields/file.php @@ -0,0 +1,19 @@ +layout('fields.field') ?> +
+ icon($field->get('icon', 'file')) ?> + +
\ No newline at end of file diff --git a/panel/views/fields/files.php b/panel/views/fields/files.php new file mode 100644 index 000000000..f82743953 --- /dev/null +++ b/panel/views/fields/files.php @@ -0,0 +1,19 @@ +layout('fields.field') ?> +
+ icon($field->get('icon', 'file')) ?> + attr([ + 'class' => ['form-input', 'form-input-tags'], + 'type' => 'text', + 'id' => $field->name(), + 'name' => $field->formName(), + 'value' => implode(', ', (array) $field->value()), + 'placeholder' => $field->placeholder(), + 'required' => $field->isRequired(), + 'disabled' => $field->isDisabled(), + 'hidden' => $field->isHidden(), + 'data-limit' => $field->limit(), + 'data-options' => Formwork\Parsers\Json::encode($field->options()), + 'data-accept' => 'options', + 'data-orderable' => $field->isOrderable(), + ]) ?>> +
\ No newline at end of file diff --git a/panel/views/fields/image.php b/panel/views/fields/image.php index 20953c0f4..4087ade4c 100644 --- a/panel/views/fields/image.php +++ b/panel/views/fields/image.php @@ -1,17 +1,19 @@ layout('fields.field') ?>
icon($field->get('icon', 'image')) ?> - attr([ - 'type' => 'text', - 'class' => ['form-input', 'form-input-image'], - 'id' => $field->name(), - 'name' => $field->formName(), - 'value' => basename($field->value() ?? ''), - 'placeholder' => $field->placeholder(), - 'readonly' => true, - 'required' => $field->isRequired(), - 'disabled' => $field->isDisabled(), - 'hidden' => $field->isHidden(), +
\ No newline at end of file diff --git a/panel/views/fields/images.php b/panel/views/fields/images.php new file mode 100644 index 000000000..0b15ee773 --- /dev/null +++ b/panel/views/fields/images.php @@ -0,0 +1,19 @@ +layout('fields.field') ?> +
+ icon($field->get('icon', 'image')) ?> + attr([ + 'class' => ['form-input', 'form-input-tags'], + 'type' => 'text', + 'id' => $field->name(), + 'name' => $field->formName(), + 'value' => implode(', ', (array) $field->value()), + 'placeholder' => $field->placeholder(), + 'required' => $field->isRequired(), + 'disabled' => $field->isDisabled(), + 'hidden' => $field->isHidden(), + 'data-limit' => $field->limit(), + 'data-options' => Formwork\Parsers\Json::encode($field->options()), + 'data-accept' => 'options', + 'data-orderable' => $field->isOrderable(), + ]) ?>> +
\ No newline at end of file diff --git a/panel/views/fields/tags.php b/panel/views/fields/tags.php index f1e92d935..0e66a33f6 100644 --- a/panel/views/fields/tags.php +++ b/panel/views/fields/tags.php @@ -2,7 +2,7 @@
icon($field->get('icon', 'tag')) ?> attr([ - 'class' => 'form-input', + 'class' => ['form-input', 'form-input-tags'], 'type' => 'text', 'id' => $field->name(), 'name' => $field->formName(), @@ -11,10 +11,9 @@ 'required' => $field->isRequired(), 'disabled' => $field->isDisabled(), 'hidden' => $field->isHidden(), - 'data-field' => 'tags', - 'data-limit' => $field->get('limit'), - 'data-options' => $field->has('options') ? Formwork\Parsers\Json::encode($field->options()) : null, - 'data-accept' => $field->get('accept', 'options'), - 'data-orderable' => $field->is('orderable', true), + 'data-limit' => $field->limit(), + 'data-options' => $field->options() ? Formwork\Parsers\Json::encode($field->options()) : null, + 'data-accept' => $field->accept(), + 'data-orderable' => $field->isOrderable(), ]) ?>>
\ No newline at end of file diff --git a/site/schemes/pages/post.yaml b/site/schemes/pages/post.yaml index df3063faf..c2ead81cb 100644 --- a/site/schemes/pages/post.yaml +++ b/site/schemes/pages/post.yaml @@ -10,7 +10,7 @@ options: layout: sections: content: - fields: [title, image, tags, summary, content] + fields: [title, coverImage, tags, summary, content] fields: summary: @@ -18,11 +18,9 @@ fields: label: '{{page.summary}}' rows: 5 - image: + coverImage: type: image - default: null label: '{{page.image}}' - placeholder: '{{page.noImage}}' tags: type: tags diff --git a/site/templates/partials/cover-image.php b/site/templates/partials/cover-image.php index a04d7949c..493f2ed8c 100644 --- a/site/templates/partials/cover-image.php +++ b/site/templates/partials/cover-image.php @@ -1,3 +1,3 @@ -has('image') && !$page->image()->isEmpty() && $page->images()->has($page->image())) : ?> -
+has('coverImage') && ($image = $page->coverImage())) : ?> +
\ No newline at end of file