Skip to content

Commit ac0fecc

Browse files
committed
Merge branch '6.x' into multisite-entry-grouping
2 parents 8b85cbb + d167065 commit ac0fecc

20 files changed

Lines changed: 347 additions & 23 deletions

File tree

resources/js/components/entries/calendar/Calendar.vue

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Month from './Month.vue';
77
import Week from './Week.vue';
88
import { Listing, StatusIndicator } from '@/components/ui';
99
import DateFormatter from '@/components/DateFormatter.js';
10-
import { getWeekDates, getCurrentDateRange } from './calendar.js';
10+
import { getWeekDates, getCurrentDateRange, getEntryDate } from './calendar.js';
1111
import { Link } from '@inertiajs/vue3';
1212
import { ToggleGroup, ToggleItem, Button, Popover, Label, Select, Heading } from '@ui';
1313
import { preferences } from '@api';
@@ -122,8 +122,7 @@ const selectedDateEntries = computed(() => {
122122
const dateStr = selectedDate.value.toString();
123123
124124
return entries.value.filter(entry => {
125-
const entryDate = new Date(entry.date?.date || entry.date);
126-
return entryDate.toISOString().split('T')[0] === dateStr;
125+
return getEntryDate(entry).toString().split('T')[0] === dateStr;
127126
});
128127
});
129128

resources/js/components/entries/calendar/Month.vue

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, Cale
33
import CalendarEntry from './MonthEntry.vue';
44
import CreateEntryButton from '../CreateEntryButton.vue';
55
import { Button } from '@ui';
6-
import { isToday, getCreateUrlDateParam } from './calendar.js';
6+
import { isToday, getCreateUrlDateParam, getEntryDate } from './calendar.js';
77
import DateFormatter from '@/components/DateFormatter.js';
8-
import { parseAbsoluteToLocal } from '@internationalized/date';
98
109
const props = defineProps({
1110
weekDays: { type: Array, required: true },
@@ -32,8 +31,7 @@ const isCurrentDay = (dayIndex) => {
3231
const getEntriesForDate = (date) => {
3332
const dateStr = date.toString();
3433
return props.entries.filter(entry => {
35-
const entryDate = parseAbsoluteToLocal(entry.date?.date || entry.date);
36-
return entryDate.toString().split('T')[0] === dateStr;
34+
return getEntryDate(entry).toString().split('T')[0] === dateStr;
3735
});
3836
};
3937

resources/js/components/entries/calendar/MonthEntry.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ const entryClasses = computed(() => ({
1313
'border-purple-500 hover:bg-purple-50 dark:hover:bg-purple-900': props.entry.status === 'scheduled'
1414
}));
1515
16-
const time = computed(() => DateFormatter.format(props.entry.date?.date || props.entry.date, 'time'))
16+
const time = computed(() => {
17+
if (props.entry.date?.format_has_time === false) return null;
18+
19+
return DateFormatter.format(props.entry.date?.date || props.entry.date, 'time');
20+
});
1721
</script>
1822
1923
<template>
@@ -25,6 +29,7 @@ const time = computed(() => DateFormatter.format(props.entry.date?.date || props
2529
>
2630
<span class="line-clamp-2" v-text="entry.title" />
2731
<span
32+
v-if="time"
2833
class="hidden @4xl:block text-2xs text-gray-400 dark:text-gray-400 group-hover/entry:dark:text-gray-300"
2934
v-text="time"
3035
/>

resources/js/components/entries/calendar/Week.vue

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import { Button } from '@ui';
66
import {
77
isToday,
88
getCreateUrlDateParam,
9+
getEntryDate,
910
} from './calendar.js';
1011
import DateFormatter from '@/components/DateFormatter.js';
11-
import { parseAbsoluteToLocal } from '@internationalized/date';
1212
1313
const props = defineProps({
1414
weekDates: { type: Array, required: true },
@@ -27,12 +27,11 @@ const visibleHours = Array.from({ length: 24 }, (_, i) => i);
2727
function getEntriesForHour(date, hour) {
2828
const dateStr = date.toString();
2929
return props.entries.filter(entry => {
30-
const entryDate = parseAbsoluteToLocal(entry.date?.date || entry.date);
30+
const entryDate = getEntryDate(entry);
3131
const entryDateStr = entryDate.toString().split('T')[0];
3232
if (entryDateStr !== dateStr) return false;
3333
34-
const entryHour = entryDate.hour;
35-
return entryHour === hour;
34+
return (entryDate.hour ?? 0) === hour;
3635
});
3736
}
3837

resources/js/components/entries/calendar/calendar.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
import { CalendarDate, CalendarDateTime, fromDate, getLocalTimeZone, startOfWeek, endOfWeek } from '@internationalized/date';
1+
import { CalendarDate, CalendarDateTime, fromDate, getLocalTimeZone, parseAbsoluteToLocal, parseDate, startOfWeek, endOfWeek } from '@internationalized/date';
22
import DateFormatter from '@/components/DateFormatter.js';
33

4+
export function getEntryDate(entry) {
5+
const date = entry.date?.date || entry.date;
6+
7+
// Fields using a format without a time save a plain date string, which can't be parsed as an absolute datetime.
8+
return date.includes('T') ? parseAbsoluteToLocal(date) : parseDate(date);
9+
}
10+
411
export function getWeekDates(currentDate) {
512
if (!currentDate) {
613
throw new Error('getWeekDates called with undefined currentDate');

resources/js/components/field-conditions/Validator.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ const ROOT_PREFIX_RE = /^\$?root\./;
1111
const isEmpty = (value) => {
1212
if (value === null || value === undefined) return true;
1313

14+
// Object.keys() would consider numbers empty.
15+
if (typeof value === 'number') return false;
16+
1417
return Array.isArray(value) ? value.length === 0 : Object.keys(value).length === 0;
1518
};
1619

resources/js/tests/FieldConditionsValidator.test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ test('it can check if value is empty', () => {
227227
last_name: 'HasselHoff',
228228
user: { email: 'david@hasselhoff.com' },
229229
favorite_foods: ['lasagna'],
230+
age: 43,
231+
zero: 0,
230232
empty_string: '',
231233
empty_array: [],
232234
empty_object: {},
@@ -238,6 +240,9 @@ test('it can check if value is empty', () => {
238240
expect(showFieldIf({ last_name: 'not empty' })).toBe(true);
239241
expect(showFieldIf({ user: 'empty' })).toBe(false);
240242
expect(showFieldIf({ favorite_foods: 'empty' })).toBe(false);
243+
expect(showFieldIf({ age: 'empty' })).toBe(false);
244+
expect(showFieldIf({ age: 'not empty' })).toBe(true);
245+
expect(showFieldIf({ zero: 'empty' })).toBe(false);
241246
expect(showFieldIf({ empty_string: 'empty' })).toBe(true);
242247
expect(showFieldIf({ empty_array: 'empty' })).toBe(true);
243248
expect(showFieldIf({ empty_object: 'empty' })).toBe(true);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { expect, test } from 'vitest';
2+
import { getEntryDate } from '@/components/entries/calendar/calendar.js';
3+
4+
test('it parses datetime values', () => {
5+
const date = getEntryDate({ date: { date: '2026-08-24T12:30:00.000Z', format_has_time: true } });
6+
7+
expect(date.toString().split('T')[0]).toBe('2026-08-24');
8+
});
9+
10+
test('it parses date-only values', () => {
11+
const date = getEntryDate({ date: { date: '2026-08-24', format_has_time: false } });
12+
13+
expect(date.toString()).toBe('2026-08-24');
14+
});
15+
16+
test('it parses values that are plain strings', () => {
17+
expect(getEntryDate({ date: '2026-08-24T12:30:00.000Z' }).toString().split('T')[0]).toBe('2026-08-24');
18+
expect(getEntryDate({ date: '2026-08-24' }).toString()).toBe('2026-08-24');
19+
});

src/Addons/Addon.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -350,18 +350,21 @@ public function config()
350350

351351
public function hasSettingsBlueprint(): bool
352352
{
353-
return $this->settingsBlueprint() !== null;
353+
return app()->bound($this->settingsBlueprintBinding());
354354
}
355355

356356
public function settingsBlueprint()
357357
{
358-
$binding = "statamic.addons.{$this->slug()}.settings_blueprint";
359-
360-
if (! app()->bound($binding)) {
358+
if (! $this->hasSettingsBlueprint()) {
361359
return null;
362360
}
363361

364-
return Blueprint::make("addons.{$this->slug()}")->setContents(app($binding));
362+
return Blueprint::make("addons.{$this->slug()}")->setContents(app($this->settingsBlueprintBinding()));
363+
}
364+
365+
private function settingsBlueprintBinding(): string
366+
{
367+
return "statamic.addons.{$this->slug()}.settings_blueprint";
365368
}
366369

367370
public function setting($key, $default = null): mixed

src/Facades/Endpoint/URL.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,11 +438,11 @@ private function ensureSiteCaches(): void
438438
$sites = Site::all();
439439

440440
self::$hasRelativeSiteCache = $sites->contains(
441-
fn ($site) => Str::startsWith((string) ($site->rawConfig()['url'] ?? ''), '/')
441+
fn ($site) => Str::startsWith((string) $site->url(), '/')
442442
);
443443

444444
self::$absoluteSiteUrlsCache = $sites
445-
->map(fn ($site) => $site->rawConfig()['url'] ?? null)
445+
->map(fn ($site) => $site->url())
446446
->filter(fn ($siteUrl) => self::isAbsolute($siteUrl))
447447
->map(fn ($siteUrl) => self::getDomainFromAbsolute($siteUrl));
448448

0 commit comments

Comments
 (0)