mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-03 02:27:57 +09:00
Closes https://github.com/webstudio-is/webstudio/issues/3510 ## Summary This PR adds a **Current Date** system resource, extends the `system` object with a `pathname` property, and enhances the **Time component** with custom date formatting capabilities to provide more flexibility and prevent hydration errors. ### Key Features 1. **Current Date System Resource** - Provides normalized date/time information that prevents React hydration errors 2. **System Pathname** - Adds `pathname` property directly to the `system` object for easy access in expressions 3. **Time Component Custom Formatting** - Adds a `format` prop with template tokens for flexible date/time display ## Motivation These changes solve important use cases: - **Current Date**: Prevents React hydration errors when displaying dynamic dates (e.g., copyright years in footers). Previously, using `new Date()` in expressions would cause server/client mismatch. - **System Pathname**: Provides easy access to the current page path in expressions without needing to parse the URL manually. - **Custom Time Formatting**: Allows users to format dates with simple templates (e.g., `YYYY-MM-DD`) without being limited to `Intl.DateTimeFormat` options, and provides a simple way to exclude specific date or time components to avoid hydration issues. ## Changes ### Current Date Resource - Created [`apps/builder/app/shared/$resources/current-date.server.ts`](apps/builder/app/shared/$resources/current-date.server.ts) - Provides date fields: `year`, `month`, `day`, `iso`, `timestamp` - All values normalized to midnight UTC (00:00:00.000Z) to prevent hydration errors - Registered in [`rest.resources-loader.ts`](apps/builder/app/routes/rest.resources-loader.ts) - Added to SystemResourceForm dropdown in UI ### System Pathname - Added `pathname` property to [`System`](packages/sdk/src/schema/pages.ts) type - Updated [`system.ts`](apps/builder/app/shared/system.ts) to compute and include pathname - Normalized empty pathname to `'/'` for consistency - Available in expressions as `system.pathname` ### Time Component Enhancements - Added [`formatDate()`](packages/sdk-components-react/src/time.tsx:353) function supporting template tokens: - `YYYY`, `YY` - year (4-digit, 2-digit) - `MM`, `M` - month (zero-padded, no padding) - `DD`, `D` - day (zero-padded, no padding) - `HH`, `H` - hours (zero-padded, no padding) - `mm`, `m` - minutes (zero-padded, no padding) - `ss`, `s` - seconds (zero-padded, no padding) - Added `format` prop to [`Time`](packages/sdk-components-react/src/time.tsx:386) component that overrides `dateStyle`/`timeStyle` when provided - Updated component logic to check for `format` prop first before falling back to `Intl.DateTimeFormat` - Added comprehensive tests for date formatting in [`time.test.ts`](packages/sdk-components-react/src/time.test.ts) - Updated component metadata in [`time.ws.ts`](packages/sdk-components-react/src/time.ws.ts) to include `format` in initialProps - Example: `format="YYYY-MM-DD HH:mm:ss"` displays as "2025-11-03 18:47:25" ### Template & Fixture Updates - Updated all CLI templates (defaults, react-router, SSG) to include `pathname` in system objects - Updated all fixture files (47 files total) to include `pathname` in system objects - All 694 tests passing - All typecheck passing ## Usage ### Current Date Resource Users can now add the Current Date resource from the Data Variables panel: 1. Click + next to Data Variables 2. Select "System Resource" 3. Choose "Current Date" from the dropdown 4. Access in expressions: - `date.year` - for copyright year - `date.month` - current month (1-12) - `date.day` - current day (1-31) - `date.iso` - ISO 8601 string - `date.timestamp` - Unix timestamp ### System Pathname Access the current page path directly: - `system.pathname` - current page path (e.g., `/about`, `/blog/post-1`) ### Time Component Custom Format Use the `format` prop for custom date/time display: ```jsx <Time datetime="2025-11-03T18:47:25Z" format="YYYY-MM-DD" /> // Displays: 2025-11-03 <Time datetime="2025-11-03T18:47:25Z" format="YYYY-MM-DD HH:mm:ss" /> // Displays: 2025-11-03 18:47:25 <Time datetime="2025-11-03T18:47:25Z" format="M/D/YYYY" /> // Displays: 11/3/2025 ``` Technical Notes - Current Date values are normalized to midnight UTC to ensure consistency throughout the day and prevent hydration mismatches - The pathname property is computed from the request URL and normalized to '/' when empty - Custom format templates in the Time component are processed before falling back to Intl.DateTimeFormat - All changes are backward compatible
40 lines
1.2 KiB
TypeScript
40 lines
1.2 KiB
TypeScript
import { json } from "@remix-run/server-runtime";
|
|
import { parseBuilderUrl } from "@webstudio-is/http-client";
|
|
import { isBuilder } from "../router-utils";
|
|
|
|
/**
|
|
* System Resource that provides current date information.
|
|
* This prevents React hydration errors when displaying dynamic dates
|
|
* (e.g., copyright years in footers) by ensuring server and client
|
|
* render the same date.
|
|
*
|
|
* All values are normalized to midnight UTC (00:00:00.000Z) to ensure
|
|
* consistency throughout the entire day, preventing hydration mismatches.
|
|
*/
|
|
export const loader = async ({ request }: { request: Request }) => {
|
|
if (isBuilder(request) === false) {
|
|
throw new Error("Only builder requests are allowed");
|
|
}
|
|
|
|
const { projectId } = parseBuilderUrl(request.url);
|
|
|
|
if (projectId === undefined) {
|
|
throw new Error("projectId is required");
|
|
}
|
|
|
|
const now = new Date();
|
|
|
|
// Normalize to midnight UTC to prevent hydration mismatches
|
|
const startOfDay = new Date(
|
|
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
|
|
);
|
|
|
|
return json({
|
|
iso: startOfDay.toISOString(),
|
|
year: startOfDay.getUTCFullYear(),
|
|
month: startOfDay.getUTCMonth() + 1, // 1-12 instead of 0-11
|
|
day: startOfDay.getUTCDate(),
|
|
timestamp: startOfDay.getTime(),
|
|
});
|
|
};
|