diff --git a/package.json b/package.json index 9be8abc4d..51b2fd3a1 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "clean:build": "node ./scripts/delete-recursively.js 'media/' '**/dist/' '**/.cache/' '**/.next/' '**/.turbo/' '**/tsconfig.tsbuildinfo' '**/payload*.tgz' '**/meta_*.json'", "clean:build:allowtgz": "node ./scripts/delete-recursively.js 'media/' '**/dist/' '**/.cache/' '**/.next/' '**/.turbo/' '**/tsconfig.tsbuildinfo' '**/meta_*.json'", "clean:cache": "node ./scripts/delete-recursively.js node_modules/.cache! packages/payload/node_modules/.cache! .next/*", - "dev": "cross-env NODE_OPTIONS=--no-deprecation tsx ./test/dev.ts", + "dev": "cross-env NODE_OPTIONS=\"--no-deprecation --max-old-space-size=16384\" tsx ./test/dev.ts", "dev:generate-db-schema": "pnpm runts ./test/generateDatabaseSchema.ts", "dev:generate-graphql-schema": "pnpm runts ./test/generateGraphQLSchema.ts", "dev:generate-importmap": "pnpm runts ./test/generateImportMap.ts", @@ -111,7 +111,7 @@ "test:int:sqlite": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" NODE_NO_WARNINGS=1 PAYLOAD_DATABASE=sqlite DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=test/jest.config.js --runInBand", "test:types": "tstyche", "test:unit": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" NODE_NO_WARNINGS=1 DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=jest.config.js --runInBand", - "translateNewKeys": "pnpm --filter translations run translateNewKeys" + "translateNewKeys": "pnpm --filter @tools/scripts run generateTranslations:core" }, "lint-staged": { "**/package.json": "sort-package-json", diff --git a/packages/next/package.json b/packages/next/package.json index a53af5713..b4a13b8ae 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -103,7 +103,6 @@ "http-status": "2.1.0", "path-to-regexp": "6.3.0", "qs-esm": "7.0.2", - "react-diff-viewer-continued": "4.0.5", "sass": "1.77.4", "uuid": "10.0.0" }, diff --git a/packages/next/src/utilities/handleServerFunctions.ts b/packages/next/src/utilities/handleServerFunctions.ts index a4737ebe9..237e1a089 100644 --- a/packages/next/src/utilities/handleServerFunctions.ts +++ b/packages/next/src/utilities/handleServerFunctions.ts @@ -11,6 +11,17 @@ import { renderDocumentSlotsHandler } from '../views/Document/renderDocumentSlot import { renderListHandler } from '../views/List/handleServerFunction.js' import { initReq } from './initReq.js' +const serverFunctions: Record = { + 'copy-data-from-locale': copyDataFromLocaleHandler, + 'form-state': buildFormStateHandler, + 'get-folder-results-component-and-data': getFolderResultsComponentAndDataHandler, + 'render-document': renderDocumentHandler, + 'render-document-slots': renderDocumentSlotsHandler, + 'render-list': renderListHandler, + 'schedule-publish': schedulePublishHandler, + 'table-state': buildTableStateHandler, +} + export const handleServerFunctions: ServerFunctionHandler = async (args) => { const { name: fnKey, args: fnArgs, config: configPromise, importMap } = args @@ -26,18 +37,6 @@ export const handleServerFunctions: ServerFunctionHandler = async (args) => { req, } - const serverFunctions = { - 'copy-data-from-locale': copyDataFromLocaleHandler as any as ServerFunction, - 'form-state': buildFormStateHandler as any as ServerFunction, - 'get-folder-results-component-and-data': - getFolderResultsComponentAndDataHandler as any as ServerFunction, - 'render-document': renderDocumentHandler as any as ServerFunction, - 'render-document-slots': renderDocumentSlotsHandler as any as ServerFunction, - 'render-list': renderListHandler as any as ServerFunction, - 'schedule-publish': schedulePublishHandler as any as ServerFunction, - 'table-state': buildTableStateHandler as any as ServerFunction, - } - const fn = serverFunctions[fnKey] if (!fn) { diff --git a/packages/next/src/views/Account/index.tsx b/packages/next/src/views/Account/index.tsx index 544cccd26..715366509 100644 --- a/packages/next/src/views/Account/index.tsx +++ b/packages/next/src/views/Account/index.tsx @@ -148,6 +148,7 @@ export async function Account({ initPageResult, params, searchParams }: AdminVie importMap: payload.importMap, serverProps: { doc: data, + hasPublishedDoc, i18n, initPageResult, locale, diff --git a/packages/next/src/views/Document/handleServerFunction.tsx b/packages/next/src/views/Document/handleServerFunction.tsx index 8838888a6..b6e529ad0 100644 --- a/packages/next/src/views/Document/handleServerFunction.tsx +++ b/packages/next/src/views/Document/handleServerFunction.tsx @@ -1,11 +1,5 @@ -import type { - Data, - DocumentPreferences, - FormState, - Locale, - PayloadRequest, - VisibleEntities, -} from 'payload' +import type { RenderDocumentServerFunction } from '@payloadcms/ui' +import type { DocumentPreferences, VisibleEntities } from 'payload' import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig' import { headers as getHeaders } from 'next/headers.js' @@ -13,26 +7,7 @@ import { getAccessResults, isEntityHidden, parseCookies } from 'payload' import { renderDocument } from './index.js' -type RenderDocumentResult = { - data: any - Document: React.ReactNode - preferences: DocumentPreferences -} - -export const renderDocumentHandler = async (args: { - collectionSlug: string - disableActions?: boolean - docID: string - drawerSlug?: string - initialData?: Data - initialState?: FormState - locale?: Locale - overrideEntityVisibility?: boolean - redirectAfterCreate?: boolean - redirectAfterDelete: boolean - redirectAfterDuplicate: boolean - req: PayloadRequest -}): Promise => { +export const renderDocumentHandler: RenderDocumentServerFunction = async (args) => { const { collectionSlug, disableActions, @@ -41,6 +16,7 @@ export const renderDocumentHandler = async (args: { initialData, locale, overrideEntityVisibility, + paramsOverride, redirectAfterCreate, redirectAfterDelete, redirectAfterDuplicate, @@ -51,6 +27,8 @@ export const renderDocumentHandler = async (args: { payload: { config }, user, }, + searchParams = {}, + versions, } = args const headers = await getHeaders() @@ -163,14 +141,15 @@ export const renderDocumentHandler = async (args: { visibleEntities, }, overrideEntityVisibility, - params: { - segments: ['collections', collectionSlug, docID], + params: paramsOverride ?? { + segments: ['collections', collectionSlug, String(docID)], }, payload, redirectAfterCreate, redirectAfterDelete, redirectAfterDuplicate, - searchParams: {}, + searchParams, + versions, viewType: 'document', }) diff --git a/packages/next/src/views/Document/index.tsx b/packages/next/src/views/Document/index.tsx index e05585a31..94ac33fcb 100644 --- a/packages/next/src/views/Document/index.tsx +++ b/packages/next/src/views/Document/index.tsx @@ -6,6 +6,7 @@ import type { DocumentViewServerPropsOnly, EditViewComponent, PayloadComponent, + RenderDocumentVersionsProperties, } from 'payload' import { DocumentInfoProvider, EditDepthProvider, HydrateAuthProvider } from '@payloadcms/ui' @@ -57,6 +58,7 @@ export const renderDocument = async ({ redirectAfterDelete, redirectAfterDuplicate, searchParams, + versions, viewType, }: { drawerSlug?: string @@ -64,6 +66,7 @@ export const renderDocument = async ({ readonly redirectAfterCreate?: boolean readonly redirectAfterDelete?: boolean readonly redirectAfterDuplicate?: boolean + versions?: RenderDocumentVersionsProperties } & AdminViewServerProps): Promise<{ data: Data Document: React.ReactNode @@ -178,6 +181,7 @@ export const renderDocument = async ({ const documentViewServerProps: DocumentViewServerPropsOnly = { doc, + hasPublishedDoc, i18n, initPageResult, locale, @@ -187,6 +191,7 @@ export const renderDocument = async ({ routeSegments: segments, searchParams, user, + versions, } if ( diff --git a/packages/next/src/views/Document/renderDocumentSlots.tsx b/packages/next/src/views/Document/renderDocumentSlots.tsx index 0eac0b7f1..8d5f7dbcc 100644 --- a/packages/next/src/views/Document/renderDocumentSlots.tsx +++ b/packages/next/src/views/Document/renderDocumentSlots.tsx @@ -11,6 +11,7 @@ import type { SanitizedGlobalConfig, SaveButtonServerPropsOnly, SaveDraftButtonServerPropsOnly, + ServerFunction, ServerProps, StaticDescription, ViewDescriptionClientProps, @@ -168,8 +169,8 @@ export const renderDocumentSlots: (args: { return components } -export const renderDocumentSlotsHandler = async ( - args: { collectionSlug: string } & DefaultServerFunctionArgs, +export const renderDocumentSlotsHandler: ServerFunction<{ collectionSlug: string }> = async ( + args, ) => { const { collectionSlug, req } = args diff --git a/packages/next/src/views/List/handleServerFunction.tsx b/packages/next/src/views/List/handleServerFunction.tsx index f4a301497..2408794df 100644 --- a/packages/next/src/views/List/handleServerFunction.tsx +++ b/packages/next/src/views/List/handleServerFunction.tsx @@ -1,4 +1,4 @@ -import type { ListPreferences, ListQuery, PayloadRequest, VisibleEntities } from 'payload' +import type { ListPreferences, ListQuery, ServerFunction, VisibleEntities } from 'payload' import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig' import { headers as getHeaders } from 'next/headers.js' @@ -11,21 +11,23 @@ type RenderListResult = { preferences: ListPreferences } -export const renderListHandler = async (args: { - collectionSlug: string - disableActions?: boolean - disableBulkDelete?: boolean - disableBulkEdit?: boolean - disableQueryPresets?: boolean - documentDrawerSlug: string - drawerSlug?: string - enableRowSelections: boolean - overrideEntityVisibility?: boolean - query: ListQuery - redirectAfterDelete: boolean - redirectAfterDuplicate: boolean - req: PayloadRequest -}): Promise => { +export const renderListHandler: ServerFunction< + { + collectionSlug: string + disableActions?: boolean + disableBulkDelete?: boolean + disableBulkEdit?: boolean + disableQueryPresets?: boolean + documentDrawerSlug: string + drawerSlug?: string + enableRowSelections: boolean + overrideEntityVisibility?: boolean + query: ListQuery + redirectAfterDelete: boolean + redirectAfterDuplicate: boolean + }, + Promise +> = async (args) => { const { collectionSlug, disableActions, diff --git a/packages/next/src/views/Version/Default/SetStepNav.tsx b/packages/next/src/views/Version/Default/SetStepNav.tsx index 53717e103..cece22878 100644 --- a/packages/next/src/views/Version/Default/SetStepNav.tsx +++ b/packages/next/src/views/Version/Default/SetStepNav.tsx @@ -1,72 +1,74 @@ 'use client' -import type { StepNavItem } from '@payloadcms/ui' -import type { ClientCollectionConfig, ClientField, ClientGlobalConfig } from 'payload' + +import type { ClientCollectionConfig, ClientGlobalConfig } from 'payload' import type React from 'react' import { getTranslation } from '@payloadcms/translations' import { useConfig, useLocale, useStepNav, useTranslation } from '@payloadcms/ui' -import { formatDate } from '@payloadcms/ui/shared' import { fieldAffectsData, formatAdminURL } from 'payload/shared' import { useEffect } from 'react' export const SetStepNav: React.FC<{ readonly collectionConfig?: ClientCollectionConfig - readonly collectionSlug?: string - readonly doc: any - readonly fields: ClientField[] readonly globalConfig?: ClientGlobalConfig - readonly globalSlug?: string readonly id?: number | string -}> = ({ id, collectionConfig, collectionSlug, doc, fields, globalConfig, globalSlug }) => { + versionToCreatedAtFormatted?: string + versionToID?: string + versionToUseAsTitle?: string +}> = ({ + id, + collectionConfig, + globalConfig, + versionToCreatedAtFormatted, + versionToID, + versionToUseAsTitle, +}) => { const { config } = useConfig() const { setStepNav } = useStepNav() const { i18n, t } = useTranslation() const locale = useLocale() useEffect(() => { - let nav: StepNavItem[] = [] - const { - admin: { dateFormat }, routes: { admin: adminRoute }, } = config - if (collectionSlug && collectionConfig) { - let docLabel = '' + if (collectionConfig) { + const collectionSlug = collectionConfig.slug - const useAsTitle = collectionConfig?.admin?.useAsTitle || 'id' - const pluralLabel = collectionConfig?.labels?.plural - const formattedDoc = doc.version ? doc.version : doc + const useAsTitle = collectionConfig.admin?.useAsTitle || 'id' + const pluralLabel = collectionConfig.labels?.plural + let docLabel = `[${t('general:untitled')}]` - if (formattedDoc) { - if (useAsTitle !== 'id') { - const titleField = fields.find((f) => { - const fieldName = 'name' in f ? f.name : undefined - return Boolean(fieldAffectsData(f) && fieldName === useAsTitle) - }) + const fields = collectionConfig.fields - if (titleField && formattedDoc[useAsTitle]) { - if ('localized' in titleField && titleField.localized) { - docLabel = formattedDoc[useAsTitle]?.[locale.code] - } else { - docLabel = formattedDoc[useAsTitle] - } - } else { - docLabel = `[${t('general:untitled')}]` - } - } else { - docLabel = doc.id - } + const titleField = fields.find( + (f) => fieldAffectsData(f) && 'name' in f && f.name === useAsTitle, + ) + + if (titleField && versionToUseAsTitle) { + docLabel = + 'localized' in titleField && titleField.localized + ? versionToUseAsTitle?.[locale.code] || docLabel + : versionToUseAsTitle + } else if (useAsTitle === 'id') { + docLabel = versionToID } - nav = [ + setStepNav([ { label: getTranslation(pluralLabel, i18n), - url: formatAdminURL({ adminRoute, path: `/collections/${collectionSlug}` }), + url: formatAdminURL({ + adminRoute, + path: `/collections/${collectionSlug}`, + }), }, { label: docLabel, - url: formatAdminURL({ adminRoute, path: `/collections/${collectionSlug}/${id}` }), + url: formatAdminURL({ + adminRoute, + path: `/collections/${collectionSlug}/${id}`, + }), }, { label: 'Versions', @@ -76,51 +78,47 @@ export const SetStepNav: React.FC<{ }), }, { - label: doc?.createdAt - ? formatDate({ date: doc.createdAt, i18n, pattern: dateFormat }) - : '', + label: versionToCreatedAtFormatted, }, - ] + ]) + return } - if (globalSlug && globalConfig) { - nav = [ + if (globalConfig) { + const globalSlug = globalConfig.slug + + setStepNav([ { label: globalConfig.label, url: formatAdminURL({ adminRoute, - path: `/globals/${globalConfig.slug}`, + path: `/globals/${globalSlug}`, }), }, { label: 'Versions', url: formatAdminURL({ adminRoute, - path: `/globals/${globalConfig.slug}/versions`, + path: `/globals/${globalSlug}/versions`, }), }, { - label: doc?.createdAt - ? formatDate({ date: doc.createdAt, i18n, pattern: dateFormat }) - : '', + label: versionToCreatedAtFormatted, }, - ] + ]) } - - setStepNav(nav) }, [ config, setStepNav, - collectionSlug, - globalSlug, - doc, id, locale, t, i18n, collectionConfig, - fields, globalConfig, + versionToUseAsTitle, + versionToCreatedAtFormatted, + versionToID, ]) return null diff --git a/packages/next/src/views/Version/Default/index.scss b/packages/next/src/views/Version/Default/index.scss index ec2c5e650..f9a3f23f7 100644 --- a/packages/next/src/views/Version/Default/index.scss +++ b/packages/next/src/views/Version/Default/index.scss @@ -5,71 +5,165 @@ width: 100%; padding-bottom: var(--spacing-view-bottom); - &__wrap { - padding-top: calc(var(--base) * 1.5); - display: flex; - flex-direction: column; - gap: var(--base); - } - - &__header-wrap { - display: flex; - flex-direction: column; - gap: calc(var(--base) / 4); - } - - &__header { - display: flex; - align-items: center; - flex-wrap: wrap; - - h2 { - margin: 0; - } - } - - &__created-at { - margin: 0; + &__toggle-locales-label { color: var(--theme-elevation-500); } - &__controls { - display: flex; - gap: var(--base); + &-controls-top { + border-bottom: 1px solid var(--theme-elevation-100); + padding: 16px var(--gutter-h) 16px var(--gutter-h); - > * { - flex-basis: 100%; + &__wrapper { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + &-actions { + display: flex; + flex-direction: row; + align-items: center; + gap: var(--base); + } + } + + h2 { + font-size: 18px; + } + } + + &-controls-bottom { + border-bottom: 1px solid var(--theme-elevation-100); + padding: 16px var(--gutter-h) 16px var(--gutter-h); + position: relative; + + // Vertical separator line + &::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 50%; + width: 1px; + background-color: var(--theme-elevation-100); + transform: translateX(-50%); // Center the line + } + + &__wrapper { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: var(--base); + gap: var(--base); + } + } + + &__time-elapsed { + color: var(--theme-elevation-500); + } + + &__version-from { + display: flex; + flex-direction: column; + gap: 5px; + + &-labels { + display: flex; + flex-direction: row; + justify-content: space-between; + } + } + + &__version-to { + display: flex; + flex-direction: column; + gap: 5px; + + &-labels { + display: flex; + flex-direction: row; + justify-content: space-between; + } + + &-version { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + background: var(--theme-elevation-50); + padding: 8px 12px; + gap: calc(var(--base) / 2); + + h2 { + font-size: 13px; + font-weight: 400; + } } } &__restore { - margin: 0 0 0 var(--base); + div { + margin-block: 0; + } } &__modifiedCheckBox { margin: 0 0 0 var(--base); + display: flex; + align-items: center; + } + + &__diff-wrap { + padding-top: var(--base); + display: flex; + flex-direction: column; + gap: var(--base); + position: relative; + + // Vertical separator line + &::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 50%; + width: 1px; + background-color: var(--theme-elevation-100); + transform: translateX(-50%); // Center the line + z-index: 2; + } } @include mid-break { - &__intro, - &__header { - display: block; - } - - &__controls { - flex-direction: column; - gap: calc(var(--base) / 4); - } - - &__restore { - margin: calc(var(--base) * 0.5) 0 0 0; + &__version-to { + &-version { + flex-direction: column; + align-items: flex-start; + } } } @include small-break { - &__wrap { + &__diff-wrap { padding-top: calc(var(--base) / 2); - gap: calc(var(--base) / 2); + } + + &__version-to, + &__version-from { + &-labels { + flex-direction: column; + align-items: flex-start; + } + } + + &-controls-top { + &__wrapper { + flex-direction: column; + align-items: flex-start; + + .view-version__modifiedCheckBox { + margin-left: 0; + } + } } } } diff --git a/packages/next/src/views/Version/Default/index.tsx b/packages/next/src/views/Version/Default/index.tsx index 69f0d43f0..0dee2d4ea 100644 --- a/packages/next/src/views/Version/Default/index.tsx +++ b/packages/next/src/views/Version/Default/index.tsx @@ -1,24 +1,27 @@ 'use client' -import type { OptionObject } from 'payload' import { CheckboxInput, + ChevronIcon, + formatTimeToNow, Gutter, + Pill, + type SelectablePill, useConfig, useDocumentInfo, + useLocale, useRouteTransition, useTranslation, } from '@payloadcms/ui' -import { formatDate } from '@payloadcms/ui/shared' import { usePathname, useRouter, useSearchParams } from 'next/navigation.js' -import React, { useEffect, useMemo, useState } from 'react' +import React, { type FormEventHandler, useCallback, useEffect, useMemo, useState } from 'react' import type { CompareOption, DefaultVersionsViewProps } from './types.js' -import Restore from '../Restore/index.js' -import { SelectComparison } from '../SelectComparison/index.js' +import { Restore } from '../Restore/index.js' import './index.scss' -import { SelectLocales } from '../SelectLocales/index.js' +import { SelectComparison } from '../SelectComparison/index.js' +import { type SelectedLocaleOnChange, SelectLocales } from '../SelectLocales/index.js' import { SelectedLocalesContext } from './SelectedLocalesContext.js' import { SetStepNav } from './SetStepNav.js' @@ -26,133 +29,163 @@ const baseClass = 'view-version' export const DefaultVersionView: React.FC = ({ canUpdate, - doc, - latestDraftVersion, - latestPublishedVersion, modifiedOnly: modifiedOnlyProp, RenderedDiff, - selectedLocales: selectedLocalesProp, - versionID, + selectedLocales: selectedLocalesFromProps, + versionFromCreatedAt, + versionFromID, + versionFromOptions, + versionToCreatedAt, + versionToCreatedAtFormatted, + VersionToCreatedAtLabel, + versionToID, + versionToStatus, + versionToUseAsTitle, }) => { const { config, getEntityConfig } = useConfig() + const { code } = useLocale() + const { i18n, t } = useTranslation() - const availableLocales = useMemo( - () => - config.localization - ? config.localization.locales.map((locale) => ({ - label: locale.label, - value: locale.code, - })) - : [], - [config.localization], - ) + const [locales, setLocales] = useState([]) + const [localeSelectorOpen, setLocaleSelectorOpen] = React.useState(false) - const { i18n } = useTranslation() - const { id, collectionSlug, globalSlug } = useDocumentInfo() + useEffect(() => { + if (config.localization) { + const updatedLocales = config.localization.locales.map((locale) => { + let label = locale.label + if (typeof locale.label !== 'string' && locale.label[code]) { + label = locale.label[code] + } + + return { + name: locale.code, + Label: label, + selected: selectedLocalesFromProps.includes(locale.code), + } as SelectablePill + }) + setLocales(updatedLocales) + } + }, [code, config.localization, selectedLocalesFromProps]) + + const { id: originalDocID, collectionSlug, globalSlug } = useDocumentInfo() const { startRouteTransition } = useRouteTransition() - const [collectionConfig] = useState(() => getEntityConfig({ collectionSlug })) + const { collectionConfig, globalConfig } = useMemo(() => { + return { + collectionConfig: getEntityConfig({ collectionSlug }), + globalConfig: getEntityConfig({ globalSlug }), + } + }, [collectionSlug, globalSlug, getEntityConfig]) - const [globalConfig] = useState(() => getEntityConfig({ globalSlug })) - - const [selectedLocales, setSelectedLocales] = useState(selectedLocalesProp) - - const [compareValue, setCompareValue] = useState() const router = useRouter() const pathname = usePathname() const searchParams = useSearchParams() const [modifiedOnly, setModifiedOnly] = useState(modifiedOnlyProp) - function onToggleModifiedOnly() { - setModifiedOnly(!modifiedOnly) - } - useEffect(() => { - // If the selected comparison doc or locales change, update URL params so that version page - // This is so that RSC can update the version comparison state - const current = new URLSearchParams(Array.from(searchParams.entries())) + const updateSearchParams = useCallback( + (args: { + modifiedOnly?: boolean + selectedLocales?: SelectablePill[] + versionFromID?: string + }) => { + // If the selected comparison doc or locales change, update URL params so that version page + // This is so that RSC can update the version comparison state + const current = new URLSearchParams(Array.from(searchParams.entries())) - if (!compareValue) { - current.delete('compareValue') - } else { - current.set('compareValue', compareValue?.value) - } + if (args?.versionFromID) { + current.set('versionFrom', args?.versionFromID) + } - if (!selectedLocales) { - current.delete('localeCodes') - } else { - current.set('localeCodes', JSON.stringify(selectedLocales.map((locale) => locale.value))) - } + if (args?.selectedLocales) { + if (!args.selectedLocales.length) { + current.delete('localeCodes') + } else { + const selectedLocaleCodes: string[] = [] + for (const locale of args.selectedLocales) { + if (locale.selected) { + selectedLocaleCodes.push(locale.name) + } + } + current.set('localeCodes', JSON.stringify(selectedLocaleCodes)) + } + } - if (modifiedOnly === false) { - current.set('modifiedOnly', 'false') - } else { - current.delete('modifiedOnly') - } + if (args?.modifiedOnly === false) { + current.set('modifiedOnly', 'false') + } else if (args?.modifiedOnly === true) { + current.delete('modifiedOnly') + } - const search = current.toString() - const query = search ? `?${search}` : '' + const search = current.toString() + const query = search ? `?${search}` : '' - // TODO: this transition occurs multiple times during the initial rendering phases, need to evaluate - startRouteTransition(() => router.push(`${pathname}${query}`)) - }, [ - compareValue, - pathname, - router, - searchParams, - selectedLocales, - modifiedOnly, - startRouteTransition, - ]) + startRouteTransition(() => router.push(`${pathname}${query}`)) + }, + [pathname, router, searchParams, startRouteTransition], + ) - const { - admin: { dateFormat }, - localization, - routes: { api: apiRoute }, - serverURL, - } = config + const onToggleModifiedOnly: FormEventHandler = useCallback( + (event) => { + const newModified = (event.target as HTMLInputElement).checked + setModifiedOnly(newModified) + updateSearchParams({ + modifiedOnly: newModified, + }) + }, + [updateSearchParams], + ) - const versionCreatedAt = doc?.updatedAt - ? formatDate({ date: doc.updatedAt, i18n, pattern: dateFormat }) - : '' + const onChangeSelectedLocales: SelectedLocaleOnChange = useCallback( + ({ locales }) => { + setLocales(locales) + updateSearchParams({ + selectedLocales: locales, + }) + }, + [updateSearchParams], + ) - const compareBaseURL = `${serverURL}${apiRoute}/${globalSlug ? 'globals/' : ''}${ - collectionSlug || globalSlug - }/versions` + const onChangeVersionFrom: (val: CompareOption) => void = useCallback( + (val) => { + updateSearchParams({ + versionFromID: val.value, + }) + }, + [updateSearchParams], + ) - const draftsEnabled = Boolean((collectionConfig || globalConfig)?.versions.drafts) + const { localization } = config + + const versionToTimeAgo = useMemo( + () => + t('version:versionAgo', { + distance: formatTimeToNow({ + date: versionToCreatedAt, + i18n, + }), + }), + [versionToCreatedAt, i18n, t], + ) + + const versionFromTimeAgo = useMemo( + () => + versionFromCreatedAt + ? t('version:versionAgo', { + distance: formatTimeToNow({ + date: versionFromCreatedAt, + i18n, + }), + }) + : undefined, + [versionFromCreatedAt, i18n, t], + ) return (
- - -
-

- {i18n.t('version:versionCreatedOn', { - version: i18n.t(doc?.autosave ? 'version:autosavedVersion' : 'version:version'), - })} -

-
-

{versionCreatedAt}

- {canUpdate && ( - - )} + +
+

{i18n.t('version:compareVersions')}

+
= ({ onToggle={onToggleModifiedOnly} /> -
+ {localization && ( + } + onClick={() => setLocaleSelectorOpen((localeSelectorOpen) => !localeSelectorOpen)} + pillStyle="light" + size="small" + > + + {t('general:locales')}:{' '} + + + {locales + .filter((locale) => locale.selected) + .map((locale) => locale.name) + .join(', ')} + + + )} +
-
- - {localization && ( - + +
+
+
+ {t('version:comparingAgainst')} + {versionFromTimeAgo && ( + {versionFromTimeAgo} + )} +
+ - )} +
+ +
+
+ {t('version:currentlyViewing')} + {versionToTimeAgo} +
+
+ {VersionToCreatedAtLabel} + {canUpdate && ( + + )} +
+
- locale.value) }} - > - {doc?.version && RenderedDiff} +
+ + + locale.name) }}> + {versionToCreatedAt && RenderedDiff}
diff --git a/packages/next/src/views/Version/Default/types.ts b/packages/next/src/views/Version/Default/types.ts index eaab33d16..7014f33f4 100644 --- a/packages/next/src/views/Version/Default/types.ts +++ b/packages/next/src/views/Version/Default/types.ts @@ -1,19 +1,25 @@ -import type { Document, OptionObject } from 'payload' - export type CompareOption = { label: React.ReactNode | string - options?: CompareOption[] - relationTo?: string value: string } -export type DefaultVersionsViewProps = { - readonly canUpdate: boolean - readonly doc: Document - readonly latestDraftVersion?: string - readonly latestPublishedVersion?: string - modifiedOnly: boolean - readonly RenderedDiff: React.ReactNode - readonly selectedLocales: OptionObject[] - readonly versionID?: string +export type VersionPill = { + id: string + Label: React.ReactNode +} + +export type DefaultVersionsViewProps = { + canUpdate: boolean + modifiedOnly: boolean + RenderedDiff: React.ReactNode + selectedLocales: string[] + versionFromCreatedAt?: string + versionFromID?: string + versionFromOptions: CompareOption[] + versionToCreatedAt?: string + versionToCreatedAtFormatted: string + VersionToCreatedAtLabel: React.ReactNode + versionToID?: string + versionToStatus?: string + versionToUseAsTitle?: string } diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/DiffCollapser/index.scss b/packages/next/src/views/Version/RenderFieldsToDiff/DiffCollapser/index.scss index 6e5a90276..d73cac43a 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/DiffCollapser/index.scss +++ b/packages/next/src/views/Version/RenderFieldsToDiff/DiffCollapser/index.scss @@ -1,26 +1,61 @@ +@import '~@payloadcms/ui/scss'; + @layer payload-default { .diff-collapser { &__toggle-button { all: unset; cursor: pointer; - // Align the chevron visually with the label text - vertical-align: 1px; + position: relative; + z-index: 1; + display: flex; + align-items: center; + + .icon { + color: var(--theme-elevation-500); + } + + &:hover { + // Apply background color but with padding, thus we use after + &::before { + content: ''; + position: absolute; + top: -(base(0.15)); + left: -(base(0.15)); + right: -(base(0.15)); + bottom: -(base(0.15)); + background-color: var(--theme-elevation-50); + border-radius: var(--style-radius-s); + z-index: -1; + } + + .iterable-diff__label { + background-color: var(--theme-elevation-50); + z-index: 1; + } + } } &__label { // Add space between label, chevron, and change count - margin: 0 calc(var(--base) * 0.25); + margin: 0 calc(var(--base) * 0.3) 0 0; + display: inline-flex; + height: 100%; } &__field-change-count { // Reset the font weight of the change count to normal font-weight: normal; + margin-left: calc(var(--base) * 0.3); + padding: calc(var(--base) * 0.1) calc(var(--base) * 0.2); + background: var(--theme-elevation-100); + border-radius: var(--style-radius-s); + font-size: 0.8rem; } - &__content { + &__content:not(.diff-collapser__content--hide-gutter) { [dir='ltr'] & { // Vertical gutter - border-left: 3px solid var(--theme-elevation-50); + border-left: 2px solid var(--theme-elevation-100); // Center-align the gutter with the chevron margin-left: 3px; // Content indentation @@ -28,7 +63,7 @@ } [dir='rtl'] & { // Vertical gutter - border-right: 3px solid var(--theme-elevation-50); + border-right: 2px solid var(--theme-elevation-100); // Center-align the gutter with the chevron margin-right: 3px; // Content indentation diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/DiffCollapser/index.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/DiffCollapser/index.tsx index 433e3e4f1..08d298945 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/DiffCollapser/index.tsx +++ b/packages/next/src/views/Version/RenderFieldsToDiff/DiffCollapser/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { ClientField } from 'payload' -import { ChevronIcon, FieldDiffLabel, Pill, useConfig, useTranslation } from '@payloadcms/ui' +import { ChevronIcon, FieldDiffLabel, useConfig, useTranslation } from '@payloadcms/ui' import { fieldIsArrayType, fieldIsBlockType } from 'payload/shared' import React, { useState } from 'react' @@ -10,45 +10,44 @@ import { countChangedFields, countChangedFieldsInRows } from '../utilities/count const baseClass = 'diff-collapser' -type Props = +type Props = { + hideGutter?: boolean + initCollapsed?: boolean + Label: React.ReactNode + locales: string[] | undefined + parentIsLocalized: boolean + valueTo: unknown +} & ( | { // fields collapser children: React.ReactNode - comparison: unknown field?: never fields: ClientField[] - initCollapsed?: boolean isIterable?: false - label: React.ReactNode - locales: string[] | undefined - parentIsLocalized: boolean - version: unknown + valueFrom: unknown } | { // iterable collapser children: React.ReactNode - comparison?: unknown field: ClientField fields?: never - initCollapsed?: boolean isIterable: true - label: React.ReactNode - locales: string[] | undefined - parentIsLocalized: boolean - version: unknown + valueFrom?: unknown } +) export const DiffCollapser: React.FC = ({ children, - comparison, field, fields, + hideGutter = false, initCollapsed = false, isIterable = false, - label, + Label, locales, parentIsLocalized, - version, + valueFrom, + valueTo, }) => { const { t } = useTranslation() const [isCollapsed, setIsCollapsed] = useState(initCollapsed) @@ -62,8 +61,8 @@ export const DiffCollapser: React.FC = ({ 'DiffCollapser: field must be an array or blocks field when isIterable is true', ) } - const comparisonRows = comparison ?? [] - const versionRows = version ?? [] + const comparisonRows = valueFrom ?? [] + const versionRows = valueTo ?? [] if (!Array.isArray(comparisonRows) || !Array.isArray(versionRows)) { throw new Error( @@ -81,18 +80,19 @@ export const DiffCollapser: React.FC = ({ }) } else { changeCount = countChangedFields({ - comparison, + comparison: valueFrom, config, fields, locales, parentIsLocalized, - version, + version: valueTo, }) } const contentClassNames = [ `${baseClass}__content`, isCollapsed && `${baseClass}__content--is-collapsed`, + hideGutter && `${baseClass}__content--hide-gutter`, ] .filter(Boolean) .join(' ') @@ -106,13 +106,14 @@ export const DiffCollapser: React.FC = ({ onClick={() => setIsCollapsed(!isCollapsed)} type="button" > - +
{Label}
+ + - {label} - {changeCount > 0 && ( - + {changeCount > 0 && isCollapsed && ( + {t('version:changedFieldsCount', { count: changeCount })} - + )}
{children}
diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/RenderVersionFieldsToDiff.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/RenderVersionFieldsToDiff.tsx index 3b2458d7b..3f8ec5278 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/RenderVersionFieldsToDiff.tsx +++ b/packages/next/src/views/Version/RenderFieldsToDiff/RenderVersionFieldsToDiff.tsx @@ -8,8 +8,14 @@ import { ShimmerEffect } from '@payloadcms/ui' import React, { Fragment, useEffect } from 'react' export const RenderVersionFieldsToDiff = ({ + parent = false, versionFields, }: { + /** + * If true, this is the parent render version fields component, not one nested in + * a field with children (e.g. group) + */ + parent?: boolean versionFields: VersionField[] }): React.ReactNode => { const [hasMounted, setHasMounted] = React.useState(false) @@ -21,7 +27,7 @@ export const RenderVersionFieldsToDiff = ({ }, []) return ( -
+
{!hasMounted ? ( diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/buildVersionFields.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/buildVersionFields.tsx index 783292db0..a7aa6a614 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/buildVersionFields.tsx +++ b/packages/next/src/views/Version/RenderFieldsToDiff/buildVersionFields.tsx @@ -1,5 +1,4 @@ import type { I18nClient } from '@payloadcms/translations' -import type { DiffMethod } from 'react-diff-viewer-continued' import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent' import { dequal } from 'dequal/lite' @@ -20,13 +19,11 @@ import { } from 'payload' import { fieldIsID, fieldShouldBeLocalized, getUniqueListBy, tabHasName } from 'payload/shared' -import { diffMethods } from './fields/diffMethods.js' import { diffComponents } from './fields/index.js' import { getFieldPathsModified } from './utilities/getFieldPathsModified.js' export type BuildVersionFieldsArgs = { clientSchemaMap: ClientFieldSchemaMap - comparisonSiblingData: object customDiffComponents: Partial< Record> > @@ -39,13 +36,15 @@ export type BuildVersionFieldsArgs = { fields: Field[] i18n: I18nClient modifiedOnly: boolean + nestingLevel?: number parentIndexPath: string parentIsLocalized: boolean parentPath: string parentSchemaPath: string req: PayloadRequest selectedLocales: string[] - versionSiblingData: object + versionFromSiblingData: object + versionToSiblingData: object } /** @@ -57,20 +56,21 @@ export type BuildVersionFieldsArgs = { */ export const buildVersionFields = ({ clientSchemaMap, - comparisonSiblingData, customDiffComponents, entitySlug, fieldPermissions, fields, i18n, modifiedOnly, + nestingLevel = 0, parentIndexPath, parentIsLocalized, parentPath, parentSchemaPath, req, selectedLocales, - versionSiblingData, + versionFromSiblingData, + versionToSiblingData, }: BuildVersionFieldsArgs): { versionFields: VersionField[] } => { @@ -112,9 +112,8 @@ export const buildVersionFields = ({ const fieldName: null | string = 'name' in field ? field.name : null - const versionValue = fieldName ? versionSiblingData?.[fieldName] : versionSiblingData - - const comparisonValue = fieldName ? comparisonSiblingData?.[fieldName] : comparisonSiblingData + const valueFrom = fieldName ? versionFromSiblingData?.[fieldName] : versionFromSiblingData + const valueTo = fieldName ? versionToSiblingData?.[fieldName] : versionToSiblingData if (isLocalized) { versionField.fieldByLocale = {} @@ -123,7 +122,6 @@ export const buildVersionFields = ({ const localizedVersionField = buildVersionField({ clientField: clientField as ClientField, clientSchemaMap, - comparisonValue: comparisonValue?.[locale], customDiffComponents, entitySlug, field, @@ -132,6 +130,7 @@ export const buildVersionFields = ({ indexPath, locale, modifiedOnly, + nestingLevel, parentIsLocalized: true, parentPath, parentSchemaPath, @@ -139,7 +138,8 @@ export const buildVersionFields = ({ req, schemaPath, selectedLocales, - versionValue: versionValue?.[locale], + valueFrom: valueFrom?.[locale], + valueTo: valueTo?.[locale], }) if (localizedVersionField) { versionField.fieldByLocale[locale] = localizedVersionField @@ -149,7 +149,6 @@ export const buildVersionFields = ({ const baseVersionField = buildVersionField({ clientField: clientField as ClientField, clientSchemaMap, - comparisonValue, customDiffComponents, entitySlug, field, @@ -157,6 +156,7 @@ export const buildVersionFields = ({ i18n, indexPath, modifiedOnly, + nestingLevel, parentIsLocalized: parentIsLocalized || ('localized' in field && field.localized), parentPath, parentSchemaPath, @@ -164,7 +164,8 @@ export const buildVersionFields = ({ req, schemaPath, selectedLocales, - versionValue, + valueFrom, + valueTo, }) if (baseVersionField) { @@ -172,7 +173,12 @@ export const buildVersionFields = ({ } } - versionFields.push(versionField) + if ( + versionField.field || + (versionField.fieldByLocale && Object.keys(versionField.fieldByLocale).length) + ) { + versionFields.push(versionField) + } } return { @@ -183,7 +189,6 @@ export const buildVersionFields = ({ const buildVersionField = ({ clientField, clientSchemaMap, - comparisonValue, customDiffComponents, entitySlug, field, @@ -192,6 +197,7 @@ const buildVersionField = ({ indexPath, locale, modifiedOnly, + nestingLevel, parentIsLocalized, parentPath, parentSchemaPath, @@ -199,26 +205,26 @@ const buildVersionField = ({ req, schemaPath, selectedLocales, - versionValue, + valueFrom, + valueTo, }: { clientField: ClientField - comparisonValue: unknown field: Field indexPath: string locale?: string modifiedOnly?: boolean + nestingLevel: number parentIsLocalized: boolean path: string schemaPath: string - versionValue: unknown + valueFrom: unknown + valueTo: unknown } & Omit< BuildVersionFieldsArgs, - 'comparisonSiblingData' | 'fields' | 'parentIndexPath' | 'versionSiblingData' + 'fields' | 'parentIndexPath' | 'versionFromSiblingData' | 'versionToSiblingData' >): BaseVersionField | null => { const fieldName: null | string = 'name' in field ? field.name : null - const diffMethod: DiffMethod = diffMethods[field.type] || 'CHARS' - const hasPermission = fieldPermissions === true || !fieldName || @@ -235,7 +241,7 @@ const buildVersionField = ({ return null } - if (modifiedOnly && dequal(versionValue, comparisonValue)) { + if (modifiedOnly && dequal(valueFrom, valueTo)) { return null } @@ -286,85 +292,110 @@ const buildVersionField = ({ parentPath, parentSchemaPath, }) - baseVersionField.tabs.push({ + const tabVersion = { name: 'name' in tab ? tab.name : null, fields: buildVersionFields({ clientSchemaMap, - comparisonSiblingData: 'name' in tab ? comparisonValue?.[tab.name] : comparisonValue, customDiffComponents, entitySlug, fieldPermissions, fields: tab.fields, i18n, modifiedOnly, + nestingLevel: nestingLevel + 1, parentIndexPath: isNamedTab ? '' : tabIndexPath, parentIsLocalized: parentIsLocalized || tab.localized, parentPath: isNamedTab ? tabPath : path, parentSchemaPath: isNamedTab ? tabSchemaPath : parentSchemaPath, req, selectedLocales, - versionSiblingData: 'name' in tab ? versionValue?.[tab.name] : versionValue, + versionFromSiblingData: 'name' in tab ? valueFrom?.[tab.name] : valueFrom, + versionToSiblingData: 'name' in tab ? valueTo?.[tab.name] : valueTo, }).versionFields, label: tab.label, - }) + } + if (tabVersion?.fields?.length) { + baseVersionField.tabs.push(tabVersion) + } + } + + if (modifiedOnly && !baseVersionField.tabs.length) { + return null } } // At this point, we are dealing with a `row`, `collapsible`, etc else if ('fields' in field) { - if (field.type === 'array' && versionValue) { - const arrayValue = Array.isArray(versionValue) ? versionValue : [] + if (field.type === 'array' && (valueTo || valueFrom)) { + const maxLength = Math.max( + Array.isArray(valueTo) ? valueTo.length : 0, + Array.isArray(valueFrom) ? valueFrom.length : 0, + ) baseVersionField.rows = [] - for (let i = 0; i < arrayValue.length; i++) { - const comparisonRow = comparisonValue?.[i] || {} - const versionRow = arrayValue?.[i] || {} + for (let i = 0; i < maxLength; i++) { + const fromRow = (Array.isArray(valueFrom) && valueFrom?.[i]) || {} + const toRow = (Array.isArray(valueTo) && valueTo?.[i]) || {} + baseVersionField.rows[i] = buildVersionFields({ clientSchemaMap, - comparisonSiblingData: comparisonRow, customDiffComponents, entitySlug, fieldPermissions, fields: field.fields, i18n, modifiedOnly, + nestingLevel: nestingLevel + 1, parentIndexPath: 'name' in field ? '' : indexPath, parentIsLocalized: parentIsLocalized || field.localized, parentPath: path + '.' + i, parentSchemaPath: schemaPath, req, selectedLocales, - versionSiblingData: versionRow, + versionFromSiblingData: fromRow, + versionToSiblingData: toRow, }).versionFields } + + if (!baseVersionField.rows?.length && modifiedOnly) { + return null + } } else { baseVersionField.fields = buildVersionFields({ clientSchemaMap, - comparisonSiblingData: comparisonValue as object, customDiffComponents, entitySlug, fieldPermissions, fields: field.fields, i18n, modifiedOnly, + nestingLevel: field.type !== 'row' ? nestingLevel + 1 : nestingLevel, parentIndexPath: 'name' in field ? '' : indexPath, parentIsLocalized: parentIsLocalized || ('localized' in field && field.localized), parentPath: 'name' in field ? path : parentPath, parentSchemaPath: 'name' in field ? schemaPath : parentSchemaPath, req, selectedLocales, - versionSiblingData: versionValue as object, + versionFromSiblingData: valueFrom as object, + versionToSiblingData: valueTo as object, }).versionFields + + if (modifiedOnly && !baseVersionField.fields?.length) { + return null + } } } else if (field.type === 'blocks') { baseVersionField.rows = [] - const blocksValue = Array.isArray(versionValue) ? versionValue : [] + const maxLength = Math.max( + Array.isArray(valueTo) ? valueTo.length : 0, + Array.isArray(valueFrom) ? valueFrom.length : 0, + ) - for (let i = 0; i < blocksValue.length; i++) { - const comparisonRow = comparisonValue?.[i] || {} - const versionRow = blocksValue[i] || {} + for (let i = 0; i < maxLength; i++) { + const fromRow = (Array.isArray(valueFrom) && valueFrom?.[i]) || {} + const toRow = (Array.isArray(valueTo) && valueTo?.[i]) || {} - const blockSlugToMatch: string = versionRow.blockType - const versionBlock = + const blockSlugToMatch: string = toRow?.blockType ?? fromRow?.blockType + const toBlock = req.payload.blocks[blockSlugToMatch] ?? ((field.blockReferences ?? field.blocks).find( (block) => typeof block !== 'string' && block.slug === blockSlugToMatch, @@ -372,62 +403,77 @@ const buildVersionField = ({ let fields = [] - if (versionRow.blockType === comparisonRow.blockType) { - fields = versionBlock.fields + if (toRow.blockType === fromRow.blockType) { + fields = toBlock.fields } else { - const comparisonBlockSlugToMatch: string = versionRow.blockType + const fromBlockSlugToMatch: string = toRow?.blockType ?? fromRow?.blockType - const comparisonBlock = - req.payload.blocks[comparisonBlockSlugToMatch] ?? + const fromBlock = + req.payload.blocks[fromBlockSlugToMatch] ?? ((field.blockReferences ?? field.blocks).find( - (block) => typeof block !== 'string' && block.slug === comparisonBlockSlugToMatch, + (block) => typeof block !== 'string' && block.slug === fromBlockSlugToMatch, ) as FlattenedBlock | undefined) - if (comparisonBlock) { - fields = getUniqueListBy( - [...versionBlock.fields, ...comparisonBlock.fields], - 'name', - ) + if (fromBlock) { + fields = getUniqueListBy([...toBlock.fields, ...fromBlock.fields], 'name') } else { - fields = versionBlock.fields + fields = toBlock.fields } } baseVersionField.rows[i] = buildVersionFields({ clientSchemaMap, - comparisonSiblingData: comparisonRow, customDiffComponents, entitySlug, fieldPermissions, fields, i18n, modifiedOnly, + nestingLevel: nestingLevel + 1, parentIndexPath: 'name' in field ? '' : indexPath, parentIsLocalized: parentIsLocalized || ('localized' in field && field.localized), parentPath: path + '.' + i, - parentSchemaPath: schemaPath + '.' + versionBlock.slug, + parentSchemaPath: schemaPath + '.' + toBlock.slug, req, selectedLocales, - versionSiblingData: versionRow, + versionFromSiblingData: fromRow, + versionToSiblingData: toRow, }).versionFields } + if (!baseVersionField.rows?.length && modifiedOnly) { + return null + } } - const clientCellProps: FieldDiffClientProps = { + const clientDiffProps: FieldDiffClientProps = { baseVersionField: { ...baseVersionField, CustomComponent: undefined, }, - comparisonValue, - diffMethod, + /** + * TODO: Change to valueFrom in 4.0 + */ + comparisonValue: valueFrom, + /** + * @deprecated remove in 4.0. Each field should handle its own diffing logic + */ + diffMethod: 'diffWordsWithSpace', field: clientField, fieldPermissions: subFieldPermissions, parentIsLocalized, - versionValue, + + nestingLevel: nestingLevel ? nestingLevel : undefined, + /** + * TODO: Change to valueTo in 4.0 + */ + versionValue: valueTo, + } + if (locale) { + clientDiffProps.locale = locale } - const serverCellProps: FieldDiffServerProps = { - ...clientCellProps, + const serverDiffProps: FieldDiffServerProps = { + ...clientDiffProps, clientField, field, i18n, @@ -436,22 +482,12 @@ const buildVersionField = ({ } baseVersionField.CustomComponent = RenderServerComponent({ - clientProps: locale - ? ({ - ...clientCellProps, - locale, - } as FieldDiffClientProps) - : clientCellProps, + clientProps: clientDiffProps, Component: CustomComponent, Fallback: DefaultComponent, importMap: req.payload.importMap, key: 'diff component', - serverProps: locale - ? ({ - ...serverCellProps, - locale, - } as FieldDiffServerProps) - : serverCellProps, + serverProps: serverDiffProps, }) return baseVersionField diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Collapsible/index.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Collapsible/index.tsx index fe6c20412..01da8257f 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Collapsible/index.tsx +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Collapsible/index.tsx @@ -13,10 +13,10 @@ const baseClass = 'collapsible-diff' export const Collapsible: CollapsibleFieldDiffClientComponent = ({ baseVersionField, - comparisonValue, + comparisonValue: valueFrom, field, parentIsLocalized, - versionValue, + versionValue: valueTo, }) => { const { i18n } = useTranslation() const { selectedLocales } = useSelectedLocales() @@ -28,16 +28,16 @@ export const Collapsible: CollapsibleFieldDiffClientComponent = ({ return (
{getTranslation(field.label, i18n)} } locales={selectedLocales} parentIsLocalized={parentIsLocalized || field.localized} - version={versionValue} + valueFrom={valueFrom} + valueTo={valueTo} > diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Date/index.scss b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Date/index.scss new file mode 100644 index 000000000..3b41515ca --- /dev/null +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Date/index.scss @@ -0,0 +1,12 @@ +@layer payload-default { + .date-diff { + p *[data-match-type='delete'] { + color: unset !important; + background-color: unset !important; + } + p *[data-match-type='create'] { + color: unset !important; + background-color: unset !important; + } + } +} diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Date/index.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Date/index.tsx new file mode 100644 index 000000000..5a228a273 --- /dev/null +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Date/index.tsx @@ -0,0 +1,73 @@ +'use client' +import type { DateFieldDiffClientComponent } from 'payload' + +import { + FieldDiffContainer, + getHTMLDiffComponents, + useConfig, + useTranslation, +} from '@payloadcms/ui' +import { formatDate } from '@payloadcms/ui/shared' + +import './index.scss' + +import React from 'react' + +const baseClass = 'date-diff' + +export const DateDiffComponent: DateFieldDiffClientComponent = ({ + comparisonValue: valueFrom, + field, + locale, + nestingLevel, + versionValue: valueTo, +}) => { + const { i18n } = useTranslation() + const { + config: { + admin: { dateFormat }, + }, + } = useConfig() + + const formattedFromDate = valueFrom + ? formatDate({ + date: typeof valueFrom === 'string' ? new Date(valueFrom) : (valueFrom as Date), + i18n, + pattern: dateFormat, + }) + : '' + + const formattedToDate = valueTo + ? formatDate({ + date: typeof valueTo === 'string' ? new Date(valueTo) : (valueTo as Date), + i18n, + pattern: dateFormat, + }) + : '' + + const { From, To } = getHTMLDiffComponents({ + fromHTML: + `

` + + formattedFromDate + + '

', + toHTML: + `

` + + formattedToDate + + '

', + tokenizeByCharacter: false, + }) + + return ( + + ) +} diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Group/index.scss b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Group/index.scss index f44b93129..f94d8b09f 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Group/index.scss +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Group/index.scss @@ -1,14 +1,4 @@ @layer payload-default { .group-diff { - &__locale-label { - background: var(--theme-elevation-100); - padding: calc(var(--base) * 0.25); - [dir='ltr'] & { - margin-right: calc(var(--base) * 0.25); - } - [dir='rtl'] & { - margin-left: calc(var(--base) * 0.25); - } - } } } diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Group/index.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Group/index.tsx index 5739fb64d..2656ef392 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Group/index.tsx +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Group/index.tsx @@ -16,11 +16,11 @@ const baseClass = 'group-diff' export const Group: GroupFieldDiffClientComponent = ({ baseVersionField, - comparisonValue, + comparisonValue: valueFrom, field, locale, parentIsLocalized, - versionValue, + versionValue: valueTo, }) => { const { i18n } = useTranslation() const { selectedLocales } = useSelectedLocales() @@ -28,9 +28,8 @@ export const Group: GroupFieldDiffClientComponent = ({ return (
diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Iterable/index.scss b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Iterable/index.scss index 577e849fb..96b430610 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Iterable/index.scss +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Iterable/index.scss @@ -1,8 +1,43 @@ @layer payload-default { .iterable-diff { + &-label-container { + position: relative; + height: 20px; + display: flex; + flex-direction: row; + height: 100%; + } + + &-label-prefix { + background-color: var(--theme-bg); + position: relative; + width: calc(var(--base) * 0.5); + height: 16px; + margin-left: calc((var(--base) * -0.5) - 5px); + margin-right: calc(var(--base) * 0.5); + + &::before { + content: ''; + position: absolute; + left: 1px; + top: 8px; + transform: translateY(-50%); + width: 6px; + height: 6px; + background-color: var(--theme-elevation-200); + border-radius: 50%; + margin-right: 5px; + } + } + &__label { + font-weight: 400; + color: var(--theme-elevation-600); + } + &__locale-label { background: var(--theme-elevation-100); - padding: calc(var(--base) * 0.25); + border-radius: var(--style-radius-s); + padding: calc(var(--base) * 0.2); // border-radius: $style-radius-m; [dir='ltr'] & { margin-right: calc(var(--base) * 0.25); @@ -18,10 +53,7 @@ } &__no-rows { - font-family: monospace; - background-color: var(--theme-elevation-50); - // padding: base(0.125) calc(var(--base) * 0.5); - // margin: base(0.125) 0; + color: var(--theme-elevation-400); } } } diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Iterable/index.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Iterable/index.tsx index 678479ca5..dbdab55b7 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Iterable/index.tsx +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Iterable/index.tsx @@ -19,18 +19,18 @@ const baseClass = 'iterable-diff' export const Iterable: React.FC = ({ baseVersionField, - comparisonValue, + comparisonValue: valueFrom, field, locale, parentIsLocalized, - versionValue, + versionValue: valueTo, }) => { const { i18n } = useTranslation() const { selectedLocales } = useSelectedLocales() const { config } = useConfig() - const versionRowCount = Array.isArray(versionValue) ? versionValue.length : 0 - const comparisonRowCount = Array.isArray(comparisonValue) ? comparisonValue.length : 0 + const versionRowCount = Array.isArray(valueTo) ? valueTo.length : 0 + const comparisonRowCount = Array.isArray(valueFrom) ? valueFrom.length : 0 const maxRows = Math.max(versionRowCount, comparisonRowCount) if (!fieldIsArrayType(field) && !fieldIsBlockType(field)) { @@ -40,10 +40,9 @@ export const Iterable: React.FC = ({ return (
= ({ } locales={selectedLocales} parentIsLocalized={parentIsLocalized} - version={versionValue} + valueFrom={valueFrom} + valueTo={valueTo} > {maxRows > 0 && (
{Array.from(Array(maxRows).keys()).map((row, i) => { - const versionRow = versionValue?.[i] || {} - const comparisonRow = comparisonValue?.[i] || {} + const versionRow = valueTo?.[i] || {} + const comparisonRow = valueFrom?.[i] || {} const { fields, versionFields } = getFieldsForRowComparison({ baseVersionField, @@ -78,12 +78,18 @@ export const Iterable: React.FC = ({ return (
+
+ {rowLabel} +
+ } locales={selectedLocales} parentIsLocalized={parentIsLocalized || field.localized} - version={versionRow} + valueFrom={comparisonRow} + valueTo={versionRow} > diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/generateLabelFromValue.ts b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/generateLabelFromValue.ts new file mode 100644 index 000000000..5f64dc1c9 --- /dev/null +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/generateLabelFromValue.ts @@ -0,0 +1,67 @@ +import type { PayloadRequest, RelationshipField, TypeWithID } from 'payload' + +import { fieldAffectsData, fieldIsPresentationalOnly, fieldShouldBeLocalized } from 'payload/shared' + +import type { PopulatedRelationshipValue } from './index.js' + +export const generateLabelFromValue = ({ + field, + locale, + parentIsLocalized, + req, + value, +}: { + field: RelationshipField + locale: string + parentIsLocalized: boolean + req: PayloadRequest + value: PopulatedRelationshipValue +}): string => { + let relatedDoc: TypeWithID + let valueToReturn: string = '' + + const relationTo: string = 'relationTo' in value ? value.relationTo : (field.relationTo as string) + + if (typeof value === 'object' && 'relationTo' in value) { + relatedDoc = value.value + } else { + // Non-polymorphic relationship + relatedDoc = value + } + + const relatedCollection = req.payload.collections[relationTo].config + + const useAsTitle = relatedCollection?.admin?.useAsTitle + const useAsTitleField = relatedCollection.fields.find( + (f) => fieldAffectsData(f) && !fieldIsPresentationalOnly(f) && f.name === useAsTitle, + ) + let titleFieldIsLocalized = false + + if (useAsTitleField && fieldAffectsData(useAsTitleField)) { + titleFieldIsLocalized = fieldShouldBeLocalized({ field: useAsTitleField, parentIsLocalized }) + } + + if (typeof relatedDoc?.[useAsTitle] !== 'undefined') { + valueToReturn = relatedDoc[useAsTitle] + } else { + valueToReturn = String(relatedDoc.id) + } + + if ( + typeof valueToReturn === 'object' && + valueToReturn && + titleFieldIsLocalized && + valueToReturn?.[locale] + ) { + valueToReturn = valueToReturn[locale] + } + + if ( + (valueToReturn && typeof valueToReturn === 'object' && valueToReturn !== null) || + typeof valueToReturn !== 'string' + ) { + valueToReturn = JSON.stringify(valueToReturn) + } + + return valueToReturn +} diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/index.scss b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/index.scss index 57f56a979..825252028 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/index.scss +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/index.scss @@ -1,15 +1,91 @@ +@import '~@payloadcms/ui/scss'; + @layer payload-default { + .relationship-diff-container .field-diff-content { + padding: 0; + background: unset; + } + + .relationship-diff-container--hasOne { + .relationship-diff { + min-width: 100%; + max-width: fit-content; + } + } + + .relationship-diff-container--hasMany .field-diff-content { + background: var(--theme-elevation-50); + padding: 10px; + + .html-diff { + display: flex; + min-width: 0; + max-width: max-content; + flex-wrap: wrap; + gap: calc(var(--base) * 0.5); + } + + .relationship-diff { + padding: calc(var(--base) * 0.15) calc(var(--base) * 0.3); + } + } + .relationship-diff { - &__locale-label { - [dir='ltr'] & { - margin-right: calc(var(--base) * 0.25); + @extend %body; + display: flex; + align-items: center; + border-radius: $style-radius-s; + border: 1px solid var(--theme-elevation-150); + position: relative; + font-family: var(--font-body); + max-height: calc(var(--base) * 3); + padding: calc(var(--base) * 0.35); + + &[data-match-type='create'] { + border-color: var(--diff-create-pill-border); + color: var(--diff-create-parent-color); + + * { + color: var(--diff-create-parent-color); } - [dir='rtl'] & { - margin-left: calc(var(--base) * 0.25); + } + + &[data-match-type='delete'] { + border-color: var(--diff-delete-pill-border); + color: var(--diff-delete-parent-color); + background-color: var(--diff-delete-pill-bg); + text-decoration-line: none !important; + + * { + color: var(--diff-delete-parent-color); + text-decoration-line: none; } - background: var(--theme-elevation-100); - padding: calc(var(--base) * 0.25); - // border-radius: $style-radius-m; + + .relationship-diff__info { + text-decoration-line: line-through; + } + } + + &__info { + font-weight: 500; + } + + &__pill { + border-radius: $style-radius-s; + margin: 0 calc(var(--base) * 0.4) 0 calc(var(--base) * 0.2); + padding: 0 calc(var(--base) * 0.1); + background-color: var(--theme-elevation-150); + color: var(--theme-elevation-750); + } + + &[data-match-type='create'] .relationship-diff__pill { + background-color: var(--diff-create-parent-bg); + color: var(--diff-create-pill-color); + } + + &[data-match-type='delete'] .relationship-diff__pill { + background-color: var(--diff-delete-parent-bg); + color: var(--diff-delete-pill-color); } } } diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/index.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/index.tsx index b697bccb0..bf99d7a53 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/index.tsx +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/index.tsx @@ -1,185 +1,281 @@ -'use client' import type { - ClientCollectionConfig, - ClientConfig, - ClientField, - RelationshipFieldDiffClientComponent, + PayloadRequest, + RelationshipField, + RelationshipFieldDiffServerComponent, + TypeWithID, } from 'payload' -import { getTranslation } from '@payloadcms/translations' -import { FieldDiffLabel, useConfig, useTranslation } from '@payloadcms/ui' -import { fieldAffectsData, fieldIsPresentationalOnly, fieldShouldBeLocalized } from 'payload/shared' -import React from 'react' -import ReactDiffViewer from 'react-diff-viewer-continued' +import { getTranslation, type I18nClient } from '@payloadcms/translations' +import { FieldDiffContainer, getHTMLDiffComponents } from '@payloadcms/ui/rsc' import './index.scss' -import { diffStyles } from '../styles.js' + +import React from 'react' + +import { generateLabelFromValue } from './generateLabelFromValue.js' const baseClass = 'relationship-diff' -type RelationshipValue = Record +export type PopulatedRelationshipValue = { relationTo: string; value: TypeWithID } | TypeWithID -const generateLabelFromValue = ( - collections: ClientCollectionConfig[], - field: ClientField, - locale: string, - value: { relationTo: string; value: RelationshipValue } | RelationshipValue, - config: ClientConfig, - parentIsLocalized: boolean, -): string => { - if (Array.isArray(value)) { - return value - .map((v) => generateLabelFromValue(collections, field, locale, v, config, parentIsLocalized)) - .filter(Boolean) // Filters out any undefined or empty values - .join(', ') - } - - let relatedDoc: RelationshipValue - let valueToReturn: RelationshipValue | string = '' - - const relationTo = 'relationTo' in field ? field.relationTo : undefined - - if (value === null || typeof value === 'undefined') { - // eslint-disable-next-line @typescript-eslint/no-base-to-string -- We want to return a string specifilly for null and undefined - return String(value) - } - - if (typeof value === 'object' && 'relationTo' in value) { - relatedDoc = value.value - } else { - // Non-polymorphic relationship - relatedDoc = value - } - - const relatedCollection = relationTo - ? collections.find( - (c) => - c.slug === - (typeof value === 'object' && 'relationTo' in value ? value.relationTo : relationTo), - ) - : null - - if (relatedCollection) { - const useAsTitle = relatedCollection?.admin?.useAsTitle - const useAsTitleField = relatedCollection.fields.find( - (f) => fieldAffectsData(f) && !fieldIsPresentationalOnly(f) && f.name === useAsTitle, - ) - let titleFieldIsLocalized = false - - if (useAsTitleField && fieldAffectsData(useAsTitleField)) { - titleFieldIsLocalized = fieldShouldBeLocalized({ field: useAsTitleField, parentIsLocalized }) - } - - if (typeof relatedDoc?.[useAsTitle] !== 'undefined') { - valueToReturn = relatedDoc[useAsTitle] - } else if (typeof relatedDoc?.id !== 'undefined') { - valueToReturn = relatedDoc.id - } else { - valueToReturn = relatedDoc - } - - if (typeof valueToReturn === 'object' && titleFieldIsLocalized && valueToReturn?.[locale]) { - valueToReturn = valueToReturn[locale] - } - } else if (relatedDoc) { - // Handle non-polymorphic `hasMany` relationships or fallback - if (typeof relatedDoc?.id !== 'undefined') { - valueToReturn = String(relatedDoc.id) - } else { - valueToReturn = relatedDoc - } - } - - if ( - (valueToReturn && typeof valueToReturn === 'object' && valueToReturn !== null) || - typeof valueToReturn !== 'string' - ) { - valueToReturn = JSON.stringify(valueToReturn) - } - - return valueToReturn -} - -export const Relationship: RelationshipFieldDiffClientComponent = ({ - comparisonValue, +export const Relationship: RelationshipFieldDiffServerComponent = ({ + comparisonValue: valueFrom, field, + i18n, locale, + nestingLevel, parentIsLocalized, - versionValue, + req, + versionValue: valueTo, }) => { - const { i18n } = useTranslation() - const { config } = useConfig() + const hasMany = 'hasMany' in field && field.hasMany + const polymorphic = Array.isArray(field.relationTo) - const placeholder = `[${i18n.t('general:noValue')}]` - - const { - config: { collections }, - } = useConfig() - - let versionToRender: string | undefined = placeholder - let comparisonToRender: string | undefined = placeholder - - if (versionValue) { - if ('hasMany' in field && field.hasMany && Array.isArray(versionValue)) { - versionToRender = - versionValue - .map((val) => - generateLabelFromValue(collections, field, locale, val, config, parentIsLocalized), - ) - .join(', ') || placeholder - } else { - versionToRender = - generateLabelFromValue( - collections, - field, - locale, - versionValue, - config, - parentIsLocalized, - ) || placeholder - } + if (hasMany) { + return ( + + ) } - if (comparisonValue) { - if ('hasMany' in field && field.hasMany && Array.isArray(comparisonValue)) { - comparisonToRender = - comparisonValue - .map((val) => - generateLabelFromValue(collections, field, locale, val, config, parentIsLocalized), - ) - .join(', ') || placeholder - } else { - comparisonToRender = - generateLabelFromValue( - collections, - field, - locale, - comparisonValue, - config, - parentIsLocalized, - ) || placeholder - } - } - - const label = - 'label' in field && typeof field.label !== 'boolean' && typeof field.label !== 'function' - ? field.label - : '' - return ( -
- - {locale && {locale}} - {getTranslation(label, i18n)} - - + ) +} + +export const SingleRelationshipDiff: React.FC<{ + field: RelationshipField + i18n: I18nClient + locale: string + nestingLevel?: number + parentIsLocalized: boolean + polymorphic: boolean + req: PayloadRequest + valueFrom: PopulatedRelationshipValue + valueTo: PopulatedRelationshipValue +}> = async (args) => { + const { + field, + i18n, + locale, + nestingLevel, + parentIsLocalized, + polymorphic, + req, + valueFrom, + valueTo, + } = args + + const ReactDOMServer = (await import('react-dom/server')).default + + const FromComponent = valueFrom ? ( + + ) : null + const ToComponent = valueTo ? ( + + ) : null + + const fromHTML = FromComponent ? ReactDOMServer.renderToStaticMarkup(FromComponent) : `

` + const toHTML = ToComponent ? ReactDOMServer.renderToStaticMarkup(ToComponent) : `

` + + const diff = getHTMLDiffComponents({ + fromHTML, + toHTML, + tokenizeByCharacter: false, + }) + + return ( + + ) +} + +const ManyRelationshipDiff: React.FC<{ + field: RelationshipField + i18n: I18nClient + locale: string + nestingLevel?: number + parentIsLocalized: boolean + polymorphic: boolean + req: PayloadRequest + valueFrom: PopulatedRelationshipValue[] | undefined + valueTo: PopulatedRelationshipValue[] | undefined +}> = async ({ + field, + i18n, + locale, + nestingLevel, + parentIsLocalized, + polymorphic, + req, + valueFrom, + valueTo, +}) => { + const ReactDOMServer = (await import('react-dom/server')).default + + const fromArr = Array.isArray(valueFrom) ? valueFrom : [] + const toArr = Array.isArray(valueTo) ? valueTo : [] + + const makeNodes = (list: PopulatedRelationshipValue[]) => + list.map((val, idx) => ( + + )) + + const fromNodes = + fromArr.length > 0 ? makeNodes(fromArr) :

+ + const toNodes = toArr.length > 0 ? makeNodes(toArr) :

+ + const fromHTML = ReactDOMServer.renderToStaticMarkup(fromNodes) + const toHTML = ReactDOMServer.renderToStaticMarkup(toNodes) + + const diff = getHTMLDiffComponents({ + fromHTML, + toHTML, + tokenizeByCharacter: false, + }) + + return ( + + ) +} + +const RelationshipDocumentDiff = ({ + field, + i18n, + locale, + parentIsLocalized, + polymorphic, + relationTo, + req, + showPill = false, + value, +}: { + field: RelationshipField + i18n: I18nClient + locale: string + parentIsLocalized: boolean + polymorphic: boolean + relationTo: string + req: PayloadRequest + showPill?: boolean + value: PopulatedRelationshipValue +}) => { + const localeToUse = + locale ?? + (req.payload.config?.localization && req.payload.config?.localization?.defaultLocale) ?? + 'en' + + const title = generateLabelFromValue({ + field, + locale: localeToUse, + parentIsLocalized, + req, + value, + }) + + let pillLabel: null | string = null + if (showPill) { + const collectionConfig = req.payload.collections[relationTo].config + pillLabel = collectionConfig.labels?.singular + ? getTranslation(collectionConfig.labels.singular, i18n) + : collectionConfig.slug + } + + return ( +
+ {pillLabel && ( + + {pillLabel} + + )} + + {title} +
) } diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Select/DiffViewer/index.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Select/DiffViewer/index.tsx deleted file mode 100644 index 22630d559..000000000 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Select/DiffViewer/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -'use client' -import React from 'react' -import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer-continued' - -export const DiffViewer: React.FC<{ - comparisonToRender: string - diffMethod: string - diffStyles: any - placeholder: string - versionToRender: string -}> = ({ comparisonToRender, diffMethod, diffStyles, placeholder, versionToRender }) => { - return ( - - ) -} diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Select/index.scss b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Select/index.scss index 9d31d6dfa..8ebe408f3 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Select/index.scss +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Select/index.scss @@ -1,15 +1,4 @@ @layer payload-default { .select-diff { - &__locale-label { - [dir='ltr'] & { - margin-right: calc(var(--base) * 0.25); - } - [dir='rtl'] & { - margin-left: calc(var(--base) * 0.25); - } - background: var(--theme-elevation-100); - padding: calc(var(--base) * 0.25); - // border-radius: $style-radius-m; - } } } diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Select/index.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Select/index.tsx index eb463a9e8..205e49a7b 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Select/index.tsx +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Select/index.tsx @@ -3,12 +3,10 @@ import type { I18nClient } from '@payloadcms/translations' import type { Option, SelectField, SelectFieldDiffClientComponent } from 'payload' import { getTranslation } from '@payloadcms/translations' -import { FieldDiffLabel, useTranslation } from '@payloadcms/ui' +import { FieldDiffContainer, getHTMLDiffComponents, useTranslation } from '@payloadcms/ui' import React from 'react' import './index.scss' -import { diffStyles } from '../styles.js' -import { DiffViewer } from './DiffViewer/index.js' const baseClass = 'select-diff' @@ -60,59 +58,58 @@ const getTranslatedOptions = (options: Option | Option[], i18n: I18nClient): str } export const Select: SelectFieldDiffClientComponent = ({ - comparisonValue, + comparisonValue: valueFrom, diffMethod, field, locale, - versionValue, + nestingLevel, + versionValue: valueTo, }) => { const { i18n } = useTranslation() - let placeholder = '' - - if (versionValue == comparisonValue) { - placeholder = `[${i18n.t('general:noValue')}]` - } - const options = 'options' in field && field.options - const comparisonToRender = - typeof comparisonValue !== 'undefined' + const renderedValueFrom = + typeof valueFrom !== 'undefined' ? getTranslatedOptions( getOptionsToRender( - typeof comparisonValue === 'string' ? comparisonValue : JSON.stringify(comparisonValue), + typeof valueFrom === 'string' ? valueFrom : JSON.stringify(valueFrom), options, field.hasMany, ), i18n, ) - : placeholder + : '' - const versionToRender = - typeof versionValue !== 'undefined' + const renderedValueTo = + typeof valueTo !== 'undefined' ? getTranslatedOptions( getOptionsToRender( - typeof versionValue === 'string' ? versionValue : JSON.stringify(versionValue), + typeof valueTo === 'string' ? valueTo : JSON.stringify(valueTo), options, field.hasMany, ), i18n, ) - : placeholder + : '' + + const { From, To } = getHTMLDiffComponents({ + fromHTML: '

' + renderedValueFrom + '

', + toHTML: '

' + renderedValueTo + '

', + tokenizeByCharacter: true, + }) return ( -
- - {locale && {locale}} - {'label' in field && getTranslation(field.label || '', i18n)} - - -
+ ) } diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Tabs/index.scss b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Tabs/index.scss index 715d8d81a..02f067d9c 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Tabs/index.scss +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Tabs/index.scss @@ -5,16 +5,5 @@ &__tab-locale:not(:first-of-type) { margin-top: var(--base); } - - &__locale-label { - background: var(--theme-elevation-100); - padding: calc(var(--base) * 0.25); - [dir='ltr'] & { - margin-right: calc(var(--base) * 0.25); - } - [dir='rtl'] & { - margin-left: calc(var(--base) * 0.25); - } - } } } diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Tabs/index.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Tabs/index.tsx index b8a437e4d..54db5cfcc 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Tabs/index.tsx +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Tabs/index.tsx @@ -19,7 +19,7 @@ import { RenderVersionFieldsToDiff } from '../../RenderVersionFieldsToDiff.js' const baseClass = 'tabs-diff' export const Tabs: TabsFieldDiffClientComponent = (props) => { - const { baseVersionField, comparisonValue, field, versionValue } = props + const { baseVersionField, comparisonValue: valueFrom, field, versionValue: valueTo } = props const { selectedLocales } = useSelectedLocales() return ( @@ -35,33 +35,32 @@ export const Tabs: TabsFieldDiffClientComponent = (props) => { if ('name' in fieldTab && selectedLocales && fieldTab.localized) { // Named localized tab return selectedLocales.map((locale, index) => { - const localizedTabProps = { + const localizedTabProps: TabProps = { ...props, - comparison: comparisonValue?.[tab.name]?.[locale], - version: versionValue?.[tab.name]?.[locale], + comparisonValue: valueFrom?.[tab.name]?.[locale], + fieldTab, + locale, + tab, + versionValue: valueTo?.[tab.name]?.[locale], } return (
- +
) }) } else if ('name' in tab && tab.name) { // Named tab - const namedTabProps = { + const namedTabProps: TabProps = { ...props, - comparison: comparisonValue?.[tab.name], - version: versionValue?.[tab.name], + comparisonValue: valueFrom?.[tab.name], + fieldTab, + tab, + versionValue: valueTo?.[tab.name], } - return + return } else { // Unnamed tab return @@ -80,12 +79,12 @@ type TabProps = { } & FieldDiffClientProps const Tab: React.FC = ({ - comparisonValue, + comparisonValue: valueFrom, fieldTab, locale, parentIsLocalized, tab, - versionValue, + versionValue: valueTo, }) => { const { i18n } = useTranslation() const { selectedLocales } = useSelectedLocales() @@ -96,9 +95,8 @@ const Tab: React.FC = ({ return ( = ({ } locales={selectedLocales} parentIsLocalized={parentIsLocalized || fieldTab.localized} - version={versionValue} + valueFrom={valueFrom} + valueTo={valueTo} > diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Text/DiffViewer/index.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Text/DiffViewer/index.tsx deleted file mode 100644 index 94473e45f..000000000 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Text/DiffViewer/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -'use client' -import React from 'react' -import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer-continued' - -export const DiffViewer: React.FC<{ - comparisonToRender: string - diffMethod: string - diffStyles: any - placeholder: string - versionToRender: string -}> = ({ comparisonToRender, diffMethod, diffStyles, placeholder, versionToRender }) => { - return ( - - ) -} diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Text/index.scss b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Text/index.scss index d0c265402..dcb659cc5 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Text/index.scss +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Text/index.scss @@ -1,15 +1,4 @@ @layer payload-default { .text-diff { - &__locale-label { - [dir='ltr'] & { - margin-right: calc(var(--base) * 0.25); - } - [dir='rtl'] & { - margin-left: calc(var(--base) * 0.25); - } - background: var(--theme-elevation-100); - padding: calc(var(--base) * 0.25); - // border-radius: $style-radius-m; - } } } diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Text/index.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Text/index.tsx index 4b6e1c3aa..4aa555adf 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Text/index.tsx +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Text/index.tsx @@ -1,51 +1,92 @@ 'use client' import type { TextFieldDiffClientComponent } from 'payload' -import { getTranslation } from '@payloadcms/translations' -import { FieldDiffLabel, useTranslation } from '@payloadcms/ui' -import React from 'react' +import { FieldDiffContainer, getHTMLDiffComponents, useTranslation } from '@payloadcms/ui' import './index.scss' -import { diffStyles } from '../styles.js' -import { DiffViewer } from './DiffViewer/index.js' + +import React from 'react' const baseClass = 'text-diff' +function formatValue(value: unknown): { + tokenizeByCharacter: boolean + value: string +} { + if (typeof value === 'string') { + return { tokenizeByCharacter: true, value } + } + if (typeof value === 'number') { + return { + tokenizeByCharacter: true, + value: String(value), + } + } + if (typeof value === 'boolean') { + return { + tokenizeByCharacter: false, + value: String(value), + } + } + + if (value && typeof value === 'object') { + return { + tokenizeByCharacter: false, + value: `
${JSON.stringify(value, null, 2)}
`, + } + } + + return { + tokenizeByCharacter: true, + value: undefined, + } +} + export const Text: TextFieldDiffClientComponent = ({ - comparisonValue, - diffMethod, + comparisonValue: valueFrom, field, locale, - versionValue, + nestingLevel, + versionValue: valueTo, }) => { const { i18n } = useTranslation() let placeholder = '' - if (versionValue == comparisonValue) { - placeholder = `[${i18n.t('general:noValue')}]` + if (valueTo == valueFrom) { + placeholder = `` } - const versionToRender: string = - typeof versionValue === 'string' ? versionValue : JSON.stringify(versionValue, null, 2) - const comparisonToRender = - typeof comparisonValue === 'string' ? comparisonValue : JSON.stringify(comparisonValue, null, 2) + const formattedValueFrom = formatValue(valueFrom) + const formattedValueTo = formatValue(valueTo) + + let tokenizeByCharacter = true + if (formattedValueFrom.value?.length) { + tokenizeByCharacter = formattedValueFrom.tokenizeByCharacter + } else if (formattedValueTo.value?.length) { + tokenizeByCharacter = formattedValueTo.tokenizeByCharacter + } + + const renderedValueFrom = formattedValueFrom.value ?? placeholder + const renderedValueTo: string = formattedValueTo.value ?? placeholder + + const { From, To } = getHTMLDiffComponents({ + fromHTML: '

' + renderedValueFrom + '

', + toHTML: '

' + renderedValueTo + '

', + tokenizeByCharacter, + }) return ( -
- - {locale && {locale}} - {'label' in field && - typeof field.label !== 'function' && - getTranslation(field.label || '', i18n)} - - -
+ ) } diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Upload/index.scss b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Upload/index.scss new file mode 100644 index 000000000..2cda867e1 --- /dev/null +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Upload/index.scss @@ -0,0 +1,121 @@ +@import '~@payloadcms/ui/scss'; + +@layer payload-default { + .upload-diff-container .field-diff-content { + padding: 0; + background: unset; + } + + .upload-diff-hasMany { + display: flex; + flex-direction: column; + gap: calc(var(--base) * 0.4); + } + + .upload-diff { + @extend %body; + min-width: 100%; + max-width: fit-content; + display: flex; + align-items: center; + background-color: var(--theme-elevation-50); + border-radius: $style-radius-s; + border: 1px solid var(--theme-elevation-150); + position: relative; + font-family: var(--font-body); + max-height: calc(var(--base) * 3); + padding: calc(var(--base) * 0.1); + + &[data-match-type='create'] { + border-color: var(--diff-create-pill-border); + color: var(--diff-create-parent-color); + + * { + color: var(--diff-create-parent-color); + } + + .upload-diff__thumbnail { + border-radius: 0px; + border-color: var(--diff-create-pill-border); + background-color: none; + } + } + + &[data-match-type='delete'] { + border-color: var(--diff-delete-pill-border); + text-decoration-line: none; + color: var(--diff-delete-parent-color); + background-color: var(--diff-delete-pill-bg); + + * { + text-decoration-line: none; + color: var(--diff-delete-parent-color); + } + + .upload-diff__thumbnail { + border-radius: 0px; + border-color: var(--diff-delete-pill-border); + background-color: none; + } + } + + &__card { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + } + + &__thumbnail { + width: calc(var(--base) * 3 - base(0.8) * 2); + height: calc(var(--base) * 3 - base(0.8) * 2); + position: relative; + overflow: hidden; + flex-shrink: 0; + border-radius: 0px; + border: 1px solid var(--theme-elevation-100); + + img, + svg { + position: absolute; + object-fit: cover; + width: 100%; + height: 100%; + border-radius: 0px; + } + } + + &__info { + flex-grow: 1; + display: flex; + align-items: flex-start; + flex-direction: column; + padding: calc(var(--base) * 0.25) calc(var(--base) * 0.6); + justify-content: space-between; + font-weight: 400; + + strong { + font-weight: 500; + } + } + + &__pill { + border-radius: $style-radius-s; + margin-left: calc(var(--base) * 0.6); + padding: 0 calc(var(--base) * 0.1); + + background-color: var(--theme-elevation-150); + color: var(--theme-elevation-750); + } + + &[data-match-type='create'] .upload-diff__pill { + background-color: var(--diff-create-parent-bg); + color: var(--diff-create-pill-color); + } + + &[data-match-type='delete'] .upload-diff__pill { + background-color: var(--diff-delete-parent-bg); + color: var(--diff-delete-pill-color); + } + } +} diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Upload/index.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Upload/index.tsx new file mode 100644 index 000000000..40f555893 --- /dev/null +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Upload/index.tsx @@ -0,0 +1,245 @@ +import type { + FileData, + PayloadRequest, + TypeWithID, + UploadField, + UploadFieldDiffServerComponent, +} from 'payload' + +import { getTranslation, type I18nClient } from '@payloadcms/translations' +import { FieldDiffContainer, File, getHTMLDiffComponents } from '@payloadcms/ui/rsc' + +import './index.scss' + +import React from 'react' + +const baseClass = 'upload-diff' + +export const Upload: UploadFieldDiffServerComponent = (args) => { + const { + comparisonValue: valueFrom, + field, + i18n, + locale, + nestingLevel, + req, + versionValue: valueTo, + } = args + + if ('hasMany' in field && field.hasMany && Array.isArray(valueTo)) { + return ( + + ) + } + + return ( + + ) +} + +export const HasManyUploadDiff: React.FC<{ + field: UploadField + i18n: I18nClient + locale: string + nestingLevel?: number + req: PayloadRequest + valueFrom: Array + valueTo: Array +}> = async (args) => { + const { field, i18n, locale, nestingLevel, req, valueFrom, valueTo } = args + const ReactDOMServer = (await import('react-dom/server')).default + + let From: React.ReactNode = '' + let To: React.ReactNode = '' + + const showCollectionSlug = Array.isArray(field.relationTo) + + const FromComponents = valueFrom + ? valueFrom.map((uploadDoc) => ( + + )) + : null + const ToComponents = valueTo + ? valueTo.map((uploadDoc) => ( + + )) + : null + + const diffResult = getHTMLDiffComponents({ + fromHTML: + `
` + + (FromComponents + ? FromComponents.map( + (component) => `
${ReactDOMServer.renderToStaticMarkup(component)}
`, + ).join('') + : '') + + '
', + toHTML: + `
` + + (ToComponents + ? ToComponents.map( + (component) => `
${ReactDOMServer.renderToStaticMarkup(component)}
`, + ).join('') + : '') + + '
', + tokenizeByCharacter: false, + }) + From = diffResult.From + To = diffResult.To + + return ( + + ) +} + +export const SingleUploadDiff: React.FC<{ + field: UploadField + i18n: I18nClient + locale: string + nestingLevel?: number + req: PayloadRequest + valueFrom: FileData & TypeWithID + valueTo: FileData & TypeWithID +}> = async (args) => { + const { field, i18n, locale, nestingLevel, req, valueFrom, valueTo } = args + + const ReactDOMServer = (await import('react-dom/server')).default + + let From: React.ReactNode = '' + let To: React.ReactNode = '' + + const showCollectionSlug = Array.isArray(field.relationTo) + + const FromComponent = valueFrom ? ( + + ) : null + const ToComponent = valueTo ? ( + + ) : null + + const fromHtml = FromComponent + ? ReactDOMServer.renderToStaticMarkup(FromComponent) + : '

' + '' + '

' + const toHtml = ToComponent + ? ReactDOMServer.renderToStaticMarkup(ToComponent) + : '

' + '' + '

' + + const diffResult = getHTMLDiffComponents({ + fromHTML: fromHtml, + toHTML: toHtml, + tokenizeByCharacter: false, + }) + From = diffResult.From + To = diffResult.To + + return ( + + ) +} + +const UploadDocumentDiff = (args: { + i18n: I18nClient + relationTo: string + req: PayloadRequest + showCollectionSlug?: boolean + uploadDoc: FileData & TypeWithID +}) => { + const { i18n, relationTo, req, showCollectionSlug, uploadDoc } = args + + const thumbnailSRC: string = + ('thumbnailURL' in uploadDoc && (uploadDoc?.thumbnailURL as string)) || uploadDoc?.url || '' + + let pillLabel: null | string = null + + if (showCollectionSlug) { + const uploadConfig = req.payload.collections[relationTo].config + pillLabel = uploadConfig.labels?.singular + ? getTranslation(uploadConfig.labels.singular, i18n) + : uploadConfig.slug + } + + return ( +
+
+
+ {thumbnailSRC?.length ? {uploadDoc?.filename} : } +
+ {pillLabel && ( +
+ {pillLabel} +
+ )} +
+ {uploadDoc?.filename} +
+
+
+ ) +} diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/diffMethods.ts b/packages/next/src/views/Version/RenderFieldsToDiff/fields/diffMethods.ts deleted file mode 100644 index ab3c90ae7..000000000 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/diffMethods.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const diffMethods = { - radio: 'WORDS_WITH_SPACE', - relationship: 'WORDS_WITH_SPACE', - select: 'WORDS_WITH_SPACE', - upload: 'WORDS_WITH_SPACE', -} diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/index.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/fields/index.ts similarity index 66% rename from packages/next/src/views/Version/RenderFieldsToDiff/fields/index.tsx rename to packages/next/src/views/Version/RenderFieldsToDiff/fields/index.ts index 33ef2025d..cc00eeab1 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/index.tsx +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/index.ts @@ -1,6 +1,7 @@ -import type { FieldDiffClientProps, FieldTypes } from 'payload' +import type { FieldDiffClientProps, FieldDiffServerProps, FieldTypes } from 'payload' import { Collapsible } from './Collapsible/index.js' +import { DateDiffComponent } from './Date/index.js' import { Group } from './Group/index.js' import { Iterable } from './Iterable/index.js' import { Relationship } from './Relationship/index.js' @@ -8,14 +9,18 @@ import { Row } from './Row/index.js' import { Select } from './Select/index.js' import { Tabs } from './Tabs/index.js' import { Text } from './Text/index.js' +import { Upload } from './Upload/index.js' -export const diffComponents: Record> = { +export const diffComponents: Record< + FieldTypes, + React.ComponentType +> = { array: Iterable, blocks: Iterable, checkbox: Text, code: Text, collapsible: Collapsible, - date: Text, + date: DateDiffComponent, email: Text, group: Group, join: null, @@ -31,5 +36,5 @@ export const diffComponents: Record { const { versionFields } = buildVersionFields(args) - return + return } diff --git a/packages/next/src/views/Version/Restore/index.scss b/packages/next/src/views/Version/Restore/index.scss index 21d1ce9b9..d05079cbf 100644 --- a/packages/next/src/views/Version/Restore/index.scss +++ b/packages/next/src/views/Version/Restore/index.scss @@ -4,6 +4,7 @@ .restore-version { cursor: pointer; display: flex; + min-width: max-content; .popup-button { display: flex; @@ -24,7 +25,11 @@ } } - &__button { + .btn { + margin-block: 0; + } + + &__restore-as-draft-button { border-top-right-radius: 0px; border-bottom-right-radius: 0px; margin-right: 2px; diff --git a/packages/next/src/views/Version/Restore/index.tsx b/packages/next/src/views/Version/Restore/index.tsx index d10de9d12..f5b80046f 100644 --- a/packages/next/src/views/Version/Restore/index.tsx +++ b/packages/next/src/views/Version/Restore/index.tsx @@ -1,5 +1,7 @@ 'use client' +import type { ClientCollectionConfig, ClientGlobalConfig, SanitizedCollectionConfig } from 'payload' + import { getTranslation } from '@payloadcms/translations' import { Button, @@ -14,23 +16,33 @@ import { import { requests } from '@payloadcms/ui/shared' import { useRouter } from 'next/navigation.js' import { formatAdminURL } from 'payload/shared' -import React, { Fragment, useCallback, useState } from 'react' - -import type { Props } from './types.js' import './index.scss' +import React, { Fragment, useCallback, useState } from 'react' + const baseClass = 'restore-version' const modalSlug = 'restore-version' -const Restore: React.FC = ({ +type Props = { + className?: string + collectionConfig?: ClientCollectionConfig + globalConfig?: ClientGlobalConfig + label: SanitizedCollectionConfig['labels']['singular'] + originalDocID: number | string + status?: string + versionDateFormatted: string + versionID: string +} + +export const Restore: React.FC = ({ className, - collectionSlug, - globalSlug, + collectionConfig, + globalConfig, label, originalDocID, status, - versionDate, + versionDateFormatted, versionID, }) => { const { @@ -38,11 +50,8 @@ const Restore: React.FC = ({ routes: { admin: adminRoute, api: apiRoute }, serverURL, }, - getEntityConfig, } = useConfig() - const collectionConfig = getEntityConfig({ collectionSlug }) - const { toggleModal } = useModal() const router = useRouter() const { i18n, t } = useTranslation() @@ -51,31 +60,31 @@ const Restore: React.FC = ({ const restoreMessage = t('version:aboutToRestoreGlobal', { label: getTranslation(label, i18n), - versionDate, + versionDate: versionDateFormatted, }) - let fetchURL = `${serverURL}${apiRoute}` - let redirectURL: string - const canRestoreAsDraft = status !== 'draft' && collectionConfig?.versions?.drafts - if (collectionSlug) { - fetchURL = `${fetchURL}/${collectionSlug}/versions/${versionID}?draft=${draft}` - redirectURL = formatAdminURL({ - adminRoute, - path: `/collections/${collectionSlug}/${originalDocID}`, - }) - } - - if (globalSlug) { - fetchURL = `${fetchURL}/globals/${globalSlug}/versions/${versionID}?draft=${draft}` - redirectURL = formatAdminURL({ - adminRoute, - path: `/globals/${globalSlug}`, - }) - } - const handleRestore = useCallback(async () => { + let fetchURL = `${serverURL}${apiRoute}` + let redirectURL: string + + if (collectionConfig) { + fetchURL = `${fetchURL}/${collectionConfig.slug}/versions/${versionID}?draft=${draft}` + redirectURL = formatAdminURL({ + adminRoute, + path: `/collections/${collectionConfig.slug}/${originalDocID}`, + }) + } + + if (globalConfig) { + fetchURL = `${fetchURL}/globals/${globalConfig.slug}/versions/${versionID}?draft=${draft}` + redirectURL = formatAdminURL({ + adminRoute, + path: `/globals/${globalConfig.slug}`, + }) + } + const res = await requests.post(fetchURL, { headers: { 'Accept-Language': i18n.language, @@ -89,16 +98,31 @@ const Restore: React.FC = ({ } else { toast.error(t('version:problemRestoringVersion')) } - }, [fetchURL, redirectURL, t, i18n, router, startRouteTransition]) + }, [ + serverURL, + apiRoute, + collectionConfig, + globalConfig, + i18n.language, + versionID, + draft, + adminRoute, + originalDocID, + startRouteTransition, + router, + t, + ]) return (
+ ) +} diff --git a/packages/next/src/views/Version/SelectComparison/VersionDrawer/index.scss b/packages/next/src/views/Version/SelectComparison/VersionDrawer/index.scss new file mode 100644 index 000000000..e1a63564e --- /dev/null +++ b/packages/next/src/views/Version/SelectComparison/VersionDrawer/index.scss @@ -0,0 +1,18 @@ +@import '~@payloadcms/ui/scss'; + +@layer payload-default { + .version-drawer { + .table { + width: 100%; + } + + .created-at-cell { + // Button reset, + underline + background: none; + border: none; + cursor: pointer; + padding: 0; + text-decoration: underline; + } + } +} diff --git a/packages/next/src/views/Version/SelectComparison/VersionDrawer/index.tsx b/packages/next/src/views/Version/SelectComparison/VersionDrawer/index.tsx new file mode 100644 index 000000000..2d2bb2d52 --- /dev/null +++ b/packages/next/src/views/Version/SelectComparison/VersionDrawer/index.tsx @@ -0,0 +1,166 @@ +'use client' +import { + Drawer, + LoadingOverlay, + toast, + useEditDepth, + useModal, + useServerFunctions, + useTranslation, +} from '@payloadcms/ui' +import { useSearchParams } from 'next/navigation.js' + +import './index.scss' + +import React, { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react' + +export const baseClass = 'version-drawer' +export const formatVersionDrawerSlug = ({ + depth, + uuid, +}: { + depth: number + uuid: string // supply when creating a new document and no id is available +}) => `version-drawer_${depth}_${uuid}` + +export const VersionDrawerContent: React.FC<{ + collectionSlug: string + docID: number | string + drawerSlug: string +}> = (props) => { + const { collectionSlug, docID, drawerSlug } = props + const { closeModal } = useModal() + const searchParams = useSearchParams() + const prevSearchParams = useRef(searchParams) + + const { renderDocument } = useServerFunctions() + + const [DocumentView, setDocumentView] = useState(undefined) + const [isLoading, setIsLoading] = useState(true) + const hasRenderedDocument = useRef(false) + const { t } = useTranslation() + + const getDocumentView = useCallback( + (docID?: number | string) => { + const fetchDocumentView = async () => { + setIsLoading(true) + + try { + const result = await renderDocument({ + collectionSlug, + docID, + drawerSlug, + paramsOverride: { + segments: ['collections', collectionSlug, String(docID), 'versions'], + }, + redirectAfterDelete: false, + redirectAfterDuplicate: false, + searchParams: Object.fromEntries(searchParams.entries()), + versions: { + disableGutter: true, + useVersionDrawerCreatedAtCell: true, + }, + }) + + if (result?.Document) { + setDocumentView(result.Document) + setIsLoading(false) + } + } catch (error) { + toast.error(error?.message || t('error:unspecific')) + closeModal(drawerSlug) + // toast.error(data?.errors?.[0].message || t('error:unspecific')) + } + } + + void fetchDocumentView() + }, + [closeModal, collectionSlug, drawerSlug, renderDocument, searchParams, t], + ) + + useEffect(() => { + if (!hasRenderedDocument.current || prevSearchParams.current !== searchParams) { + prevSearchParams.current = searchParams + getDocumentView(docID) + hasRenderedDocument.current = true + } + }, [docID, getDocumentView, searchParams]) + + if (isLoading) { + return + } + + return DocumentView +} +export const VersionDrawer: React.FC<{ + collectionSlug: string + docID: number | string + drawerSlug: string +}> = (props) => { + const { collectionSlug, docID, drawerSlug } = props + const { t } = useTranslation() + + return ( + + + + ) +} + +export const useVersionDrawer = ({ + collectionSlug, + docID, +}: { + collectionSlug: string + docID: number | string +}) => { + const drawerDepth = useEditDepth() + const uuid = useId() + const { closeModal, modalState, openModal, toggleModal } = useModal() + const [isOpen, setIsOpen] = useState(false) + + const drawerSlug = formatVersionDrawerSlug({ + depth: drawerDepth, + uuid, + }) + + useEffect(() => { + setIsOpen(Boolean(modalState[drawerSlug]?.isOpen)) + }, [modalState, drawerSlug]) + + const toggleDrawer = useCallback(() => { + toggleModal(drawerSlug) + }, [toggleModal, drawerSlug]) + + const closeDrawer = useCallback(() => { + closeModal(drawerSlug) + }, [drawerSlug, closeModal]) + + const openDrawer = useCallback(() => { + openModal(drawerSlug) + }, [drawerSlug, openModal]) + + const MemoizedDrawer = useMemo(() => { + return () => ( + + ) + }, [collectionSlug, docID, drawerSlug]) + + return useMemo( + () => ({ + closeDrawer, + Drawer: MemoizedDrawer, + drawerDepth, + drawerSlug, + isDrawerOpen: isOpen, + openDrawer, + toggleDrawer, + }), + [MemoizedDrawer, closeDrawer, drawerDepth, drawerSlug, isOpen, openDrawer, toggleDrawer], + ) +} diff --git a/packages/next/src/views/Version/SelectComparison/index.scss b/packages/next/src/views/Version/SelectComparison/index.scss index 5de71f857..f8c75f451 100644 --- a/packages/next/src/views/Version/SelectComparison/index.scss +++ b/packages/next/src/views/Version/SelectComparison/index.scss @@ -1,15 +1,9 @@ +@import '~@payloadcms/ui/scss'; + @layer payload-default { .compare-version { - &__error-loading { - border: 1px solid var(--theme-error-500); - min-height: calc(var(--base) * 2); - padding: calc(var(--base) * 0.5) calc(var(--base) * 0.75); - background-color: var(--theme-error-100); - color: var(--theme-elevation-0); - } - - &__label { - margin-bottom: calc(var(--base) * 0.25); + &-moreVersions { + color: var(--theme-elevation-500); } } } diff --git a/packages/next/src/views/Version/SelectComparison/index.tsx b/packages/next/src/views/Version/SelectComparison/index.tsx index daa21a256..c3d909183 100644 --- a/packages/next/src/views/Version/SelectComparison/index.tsx +++ b/packages/next/src/views/Version/SelectComparison/index.tsx @@ -1,218 +1,67 @@ 'use client' -import type { PaginatedDocs, Where } from 'payload' +import { fieldBaseClass, ReactSelect, useTranslation } from '@payloadcms/ui' +import React, { memo, useCallback, useMemo } from 'react' -import { - fieldBaseClass, - Pill, - ReactSelect, - useConfig, - useDocumentInfo, - useTranslation, -} from '@payloadcms/ui' -import { formatDate } from '@payloadcms/ui/shared' -import { stringify } from 'qs-esm' -import React, { useCallback, useEffect, useState } from 'react' +import type { CompareOption } from '../Default/types.js' + +import './index.scss' import type { Props } from './types.js' -import { renderPill } from '../../Versions/cells/AutosaveCell/index.js' -import './index.scss' +import { useVersionDrawer } from './VersionDrawer/index.js' const baseClass = 'compare-version' -const maxResultsPerRequest = 10 - -const baseOptions = [] - -export const SelectComparison: React.FC = (props) => { +export const SelectComparison: React.FC = memo((props) => { const { - baseURL, - draftsEnabled, - latestDraftVersion, - latestPublishedVersion, - onChange, - parentID, - value, - versionID, + collectionSlug, + docID, + onChange: onChangeFromProps, + versionFromID, + versionFromOptions, } = props + const { t } = useTranslation() - const { - config: { - admin: { dateFormat }, - localization, - }, - } = useConfig() + const { Drawer, openDrawer } = useVersionDrawer({ collectionSlug, docID }) - const { hasPublishedDoc } = useDocumentInfo() + const options = useMemo(() => { + return [ + ...versionFromOptions, + { + label: {t('version:moreVersions')}, + value: 'more', + }, + ] + }, [t, versionFromOptions]) - const [options, setOptions] = useState< - { - label: React.ReactNode | string - value: string - }[] - >(baseOptions) - const [lastLoadedPage, setLastLoadedPage] = useState(1) - const [errorLoading, setErrorLoading] = useState('') - const { i18n, t } = useTranslation() - const loadedAllOptionsRef = React.useRef(false) + const currentOption = useMemo( + () => versionFromOptions.find((option) => option.value === versionFromID), + [versionFromOptions, versionFromID], + ) - const getResults = useCallback( - async ({ lastLoadedPage: lastLoadedPageArg }) => { - if (loadedAllOptionsRef.current) { + const onChange = useCallback( + (val: CompareOption) => { + if (val.value === 'more') { + openDrawer() return } - const query: { - [key: string]: unknown - where: Where - } = { - depth: 0, - limit: maxResultsPerRequest, - page: lastLoadedPageArg, - where: { - and: [ - { - id: { - not_equals: versionID, - }, - }, - ], - }, - } - - if (parentID) { - query.where.and.push({ - parent: { - equals: parentID, - }, - }) - } - - if (localization && draftsEnabled) { - query.where.and.push({ - snapshot: { - not_equals: true, - }, - }) - } - - const search = stringify(query) - - const response = await fetch(`${baseURL}?${search}`, { - credentials: 'include', - headers: { - 'Accept-Language': i18n.language, - }, - }) - - if (response.ok) { - const data: PaginatedDocs = await response.json() - - if (data.docs.length > 0) { - const versionInfo = { - draft: { - currentLabel: t('version:currentDraft'), - latestVersion: latestDraftVersion, - pillStyle: undefined, - previousLabel: t('version:draft'), - }, - published: { - currentLabel: t('version:currentPublishedVersion'), - // The latest published version does not necessarily equal the current published version, - // because the latest published version might have been unpublished in the meantime. - // Hence, we should only use the latest published version if there is a published document. - latestVersion: hasPublishedDoc ? latestPublishedVersion : undefined, - pillStyle: 'success', - previousLabel: t('version:previouslyPublished'), - }, - } - - const additionalOptions = data.docs.map((doc) => { - const status = doc.version._status - let publishedLocalePill = null - const publishedLocale = doc.publishedLocale || undefined - const { currentLabel, latestVersion, pillStyle, previousLabel } = - versionInfo[status] || {} - - if (localization && localization?.locales && publishedLocale) { - const localeCode = Array.isArray(publishedLocale) - ? publishedLocale[0] - : publishedLocale - - const locale = localization.locales.find((loc) => loc.code === localeCode) - const formattedLabel = locale?.label?.[i18n?.language] || locale?.label - - if (formattedLabel) { - publishedLocalePill = {formattedLabel} - } - } - - return { - label: ( -
- {formatDate({ date: doc.updatedAt, i18n, pattern: dateFormat })} -    - {renderPill(doc, latestVersion, currentLabel, previousLabel, pillStyle)} - {publishedLocalePill} -
- ), - value: doc.id, - } - }) - - setOptions((existingOptions) => [...existingOptions, ...additionalOptions]) - - if (!data.hasNextPage) { - loadedAllOptionsRef.current = true - } - setLastLoadedPage(data.page) - } - } else { - setErrorLoading(t('error:unspecific')) - } + onChangeFromProps(val) }, - [dateFormat, baseURL, parentID, versionID, t, i18n, latestDraftVersion, latestPublishedVersion], + [onChangeFromProps, openDrawer], ) - useEffect(() => { - if (!i18n.dateFNS) { - // If dateFNS is not loaded, we can't format the date in getResults - return - } - void getResults({ lastLoadedPage: 1 }) - }, [getResults, i18n.dateFNS]) - - const filteredOptions = options.filter( - (option, index, self) => self.findIndex((t) => t.value === option.value) === index, - ) - - useEffect(() => { - if (filteredOptions.length > 0 && !value) { - onChange(filteredOptions[0]) - } - }, [filteredOptions, value, onChange]) - return ( -
-
{t('version:compareVersion')}
- {!errorLoading && ( - { - void getResults({ lastLoadedPage: lastLoadedPage + 1 }) - }} - options={filteredOptions} - placeholder={t('version:selectVersionToCompare')} - value={value} - /> - )} - {errorLoading &&
{errorLoading}
} +
+ +
) -} +}) diff --git a/packages/next/src/views/Version/SelectComparison/types.ts b/packages/next/src/views/Version/SelectComparison/types.ts index cbda8fc5e..11c9808f1 100644 --- a/packages/next/src/views/Version/SelectComparison/types.ts +++ b/packages/next/src/views/Version/SelectComparison/types.ts @@ -3,14 +3,11 @@ import type { PaginatedDocs, SanitizedCollectionConfig } from 'payload' import type { CompareOption } from '../Default/types.js' export type Props = { - baseURL: string - draftsEnabled?: boolean - latestDraftVersion?: string - latestPublishedVersion?: string + collectionSlug: string + docID: number | string onChange: (val: CompareOption) => void - parentID?: number | string - value: CompareOption - versionID: string + versionFromID?: string + versionFromOptions: CompareOption[] } type CLEAR = { diff --git a/packages/next/src/views/Version/SelectLocales/index.scss b/packages/next/src/views/Version/SelectLocales/index.scss deleted file mode 100644 index a3ec33aab..000000000 --- a/packages/next/src/views/Version/SelectLocales/index.scss +++ /dev/null @@ -1,9 +0,0 @@ -@layer payload-default { - .select-version-locales { - flex-grow: 1; - - &__label { - margin-bottom: calc(var(--base) * 0.25); - } - } -} diff --git a/packages/next/src/views/Version/SelectLocales/index.tsx b/packages/next/src/views/Version/SelectLocales/index.tsx index 4f67ca78c..b14884979 100644 --- a/packages/next/src/views/Version/SelectLocales/index.tsx +++ b/packages/next/src/views/Version/SelectLocales/index.tsx @@ -1,41 +1,41 @@ 'use client' -import { ReactSelect, useLocale, useTranslation } from '@payloadcms/ui' + +import { AnimateHeight } from '@payloadcms/ui' +import { PillSelector, type SelectablePill } from '@payloadcms/ui' import React from 'react' -import type { Props } from './types.js' - -import './index.scss' - const baseClass = 'select-version-locales' -export const SelectLocales: React.FC = ({ onChange, options, value }) => { - const { t } = useTranslation() - const { code } = useLocale() - - const format = (items) => { - return items.map((item) => { - if (typeof item.label === 'string') { - return item - } - if (typeof item.label !== 'string' && item.label[code]) { - return { - label: item.label[code], - value: item.value, - } - } - }) - } +export type SelectedLocaleOnChange = (args: { locales: SelectablePill[] }) => void +export type Props = { + locales: SelectablePill[] + localeSelectorOpen: boolean + onChange: SelectedLocaleOnChange +} +export const SelectLocales: React.FC = ({ locales, localeSelectorOpen, onChange }) => { return ( -
-
{t('version:showLocales')}
- + { + const newLocales = locales.map((locale) => { + if (locale.name === pill.name) { + return { + ...locale, + selected: !pill.selected, + } + } else { + return locale + } + }) + onChange({ locales: newLocales }) + }} + pills={locales} /> -
+ ) } diff --git a/packages/next/src/views/Version/SelectLocales/types.ts b/packages/next/src/views/Version/SelectLocales/types.ts deleted file mode 100644 index 70f8e23d3..000000000 --- a/packages/next/src/views/Version/SelectLocales/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { OptionObject } from 'payload' - -export type Props = { - onChange: (options: OptionObject[]) => void - options: OptionObject[] - value: OptionObject[] -} diff --git a/packages/next/src/views/Version/VersionPillLabel/VersionPillLabel.tsx b/packages/next/src/views/Version/VersionPillLabel/VersionPillLabel.tsx new file mode 100644 index 000000000..1ca29a7f4 --- /dev/null +++ b/packages/next/src/views/Version/VersionPillLabel/VersionPillLabel.tsx @@ -0,0 +1,122 @@ +'use client' + +import { Pill, useConfig, useTranslation } from '@payloadcms/ui' +import { formatDate } from '@payloadcms/ui/shared' +import React from 'react' + +import './index.scss' +import { getVersionLabel } from './getVersionLabel.js' + +const baseClass = 'version-pill-label' + +const renderPill = (label: React.ReactNode, pillStyle: Parameters[0]['pillStyle']) => { + return ( + + {label} + + ) +} + +export const VersionPillLabel: React.FC<{ + currentlyPublishedVersion?: { + id: number | string + updatedAt: string + } + disableDate?: boolean + + doc: { + [key: string]: unknown + id: number | string + publishedLocale?: string + updatedAt?: string + version: { + [key: string]: unknown + _status: string + } + } + /** + * By default, the date is displayed first, followed by the version label. + * @default false + */ + labelFirst?: boolean + labelOverride?: React.ReactNode + /** + * @default 'pill' + */ + labelStyle?: 'pill' | 'text' + labelSuffix?: React.ReactNode + latestDraftVersion?: { + id: number | string + updatedAt: string + } +}> = ({ + currentlyPublishedVersion, + disableDate = false, + doc, + labelFirst = false, + labelOverride, + labelStyle = 'pill', + labelSuffix, + latestDraftVersion, +}) => { + const { + config: { + admin: { dateFormat }, + localization, + }, + } = useConfig() + const { i18n, t } = useTranslation() + + const { label, pillStyle } = getVersionLabel({ + currentlyPublishedVersion, + latestDraftVersion, + t, + version: doc, + }) + const labelText: React.ReactNode = ( + + {labelOverride || label} + {labelSuffix} + + ) + + const showDate = !disableDate && doc.updatedAt + const formattedDate = showDate + ? formatDate({ date: doc.updatedAt, i18n, pattern: dateFormat }) + : null + + const localeCode = Array.isArray(doc.publishedLocale) + ? doc.publishedLocale[0] + : doc.publishedLocale + + const locale = + localization && localization?.locales + ? localization.locales.find((loc) => loc.code === localeCode) + : null + const localeLabel = locale ? locale?.label?.[i18n?.language] || locale?.label : null + + return ( +
+ {labelFirst ? ( + + {labelStyle === 'pill' ? ( + renderPill(labelText, pillStyle) + ) : ( + {labelText} + )} + {showDate && {formattedDate}} + + ) : ( + + {showDate && {formattedDate}} + {labelStyle === 'pill' ? ( + renderPill(labelText, pillStyle) + ) : ( + {labelText} + )} + + )} + {localeLabel && {localeLabel}} +
+ ) +} diff --git a/packages/next/src/views/Version/VersionPillLabel/getVersionLabel.ts b/packages/next/src/views/Version/VersionPillLabel/getVersionLabel.ts new file mode 100644 index 000000000..49ad84e33 --- /dev/null +++ b/packages/next/src/views/Version/VersionPillLabel/getVersionLabel.ts @@ -0,0 +1,62 @@ +import type { TFunction } from '@payloadcms/translations' +import type { Pill } from '@payloadcms/ui' + +type Args = { + currentlyPublishedVersion?: { + id: number | string + updatedAt: string + } + latestDraftVersion?: { + id: number | string + updatedAt: string + } + t: TFunction + version: { + id: number | string + version: { _status?: string } + } +} + +/** + * Gets the appropriate version label and version pill styling + * given existing versions and the current version status. + */ +export function getVersionLabel({ + currentlyPublishedVersion, + latestDraftVersion, + t, + version, +}: Args): { + label: string + name: 'currentDraft' | 'currentlyPublished' | 'draft' | 'previouslyPublished' | 'published' + pillStyle: Parameters[0]['pillStyle'] +} { + const publishedNewerThanDraft = + currentlyPublishedVersion?.updatedAt > latestDraftVersion?.updatedAt + + if (version.version._status === 'draft') { + if (publishedNewerThanDraft) { + return { + name: 'draft', + label: t('version:draft'), + pillStyle: 'light', + } + } else { + return { + name: version.id === latestDraftVersion?.id ? 'currentDraft' : 'draft', + label: + version.id === latestDraftVersion?.id ? t('version:currentDraft') : t('version:draft'), + pillStyle: 'light', + } + } + } else { + const isCurrentlyPublished = version.id === currentlyPublishedVersion?.id + return { + name: isCurrentlyPublished ? 'currentlyPublished' : 'previouslyPublished', + label: isCurrentlyPublished + ? t('version:currentlyPublished') + : t('version:previouslyPublished'), + pillStyle: isCurrentlyPublished ? 'success' : 'light', + } + } +} diff --git a/packages/next/src/views/Version/VersionPillLabel/index.scss b/packages/next/src/views/Version/VersionPillLabel/index.scss new file mode 100644 index 000000000..fb6a15c6b --- /dev/null +++ b/packages/next/src/views/Version/VersionPillLabel/index.scss @@ -0,0 +1,26 @@ +@import '~@payloadcms/ui/scss'; + +@layer payload-default { + .version-pill-label { + display: flex; + align-items: center; + gap: calc(var(--base) / 2); + + &-text { + font-weight: 500; + } + + &-date { + color: var(--theme-elevation-500); + } + } + + @include small-break { + .version-pill-label { + // Column + flex-direction: column; + align-items: flex-start; + gap: 0; + } + } +} diff --git a/packages/next/src/views/Version/fetchVersions.ts b/packages/next/src/views/Version/fetchVersions.ts new file mode 100644 index 000000000..85188c1cb --- /dev/null +++ b/packages/next/src/views/Version/fetchVersions.ts @@ -0,0 +1,192 @@ +import { + logError, + type PaginatedDocs, + type PayloadRequest, + type SelectType, + type Sort, + type TypeWithVersion, + type User, + type Where, +} from 'payload' + +export const fetchVersion = async ({ + id, + collectionSlug, + depth, + globalSlug, + locale, + overrideAccess, + req, + select, + user, +}: { + collectionSlug?: string + depth?: number + globalSlug?: string + id: number | string + locale?: 'all' | ({} & string) + overrideAccess?: boolean + req: PayloadRequest + select?: SelectType + user?: User +}): Promise> => { + try { + if (collectionSlug) { + return (await req.payload.findVersionByID({ + id: String(id), + collection: collectionSlug, + depth, + locale, + overrideAccess, + req, + select, + user, + })) as TypeWithVersion + } else if (globalSlug) { + return (await req.payload.findGlobalVersionByID({ + id: String(id), + slug: globalSlug, + depth, + locale, + overrideAccess, + req, + select, + user, + })) as TypeWithVersion + } + } catch (err) { + logError({ err, payload: req.payload }) + return null + } +} + +export const fetchVersions = async ({ + collectionSlug, + depth, + draft, + globalSlug, + limit, + locale, + overrideAccess, + page, + parentID, + req, + select, + sort, + user, + where: whereFromArgs, +}: { + collectionSlug?: string + depth?: number + draft?: boolean + globalSlug?: string + limit?: number + locale?: 'all' | ({} & string) + overrideAccess?: boolean + page?: number + parentID?: number | string + req: PayloadRequest + select?: SelectType + sort?: Sort + user?: User + where?: Where +}): Promise>> => { + const where: Where = { and: [...(whereFromArgs ? [whereFromArgs] : [])] } + + try { + if (collectionSlug) { + if (parentID) { + where.and.push({ + parent: { + equals: parentID, + }, + }) + } + return (await req.payload.findVersions({ + collection: collectionSlug, + depth, + draft, + limit, + locale, + overrideAccess, + page, + req, + select, + sort, + user, + where, + })) as PaginatedDocs> + } else if (globalSlug) { + return (await req.payload.findGlobalVersions({ + slug: globalSlug, + depth, + limit, + locale, + overrideAccess, + page, + req, + select, + sort, + user, + where, + })) as PaginatedDocs> + } + } catch (err) { + logError({ err, payload: req.payload }) + + return null + } +} + +export const fetchLatestVersion = async ({ + collectionSlug, + depth, + globalSlug, + locale, + overrideAccess, + parentID, + req, + select, + status, + user, + where, +}: { + collectionSlug?: string + depth?: number + globalSlug?: string + locale?: 'all' | ({} & string) + overrideAccess?: boolean + parentID?: number | string + req: PayloadRequest + select?: SelectType + status: 'draft' | 'published' + user?: User + where?: Where +}): Promise> => { + const and: Where[] = [ + { + 'version._status': { + equals: status, + }, + }, + ...(where ? [where] : []), + ] + + const latest = await fetchVersions({ + collectionSlug, + depth, + draft: true, + globalSlug, + limit: 1, + locale, + overrideAccess, + parentID, + req, + select, + sort: '-updatedAt', + user, + where: { and }, + }) + + return latest?.docs?.length ? (latest.docs[0] as TypeWithVersion) : null +} diff --git a/packages/next/src/views/Version/index.tsx b/packages/next/src/views/Version/index.tsx index bd30670ca..2c2cdebf2 100644 --- a/packages/next/src/views/Version/index.tsx +++ b/packages/next/src/views/Version/index.tsx @@ -1,24 +1,28 @@ import type { - Document, DocumentViewServerProps, Locale, - OptionObject, SanitizedCollectionPermission, SanitizedGlobalPermission, + TypeWithVersion, } from 'payload' +import { formatDate } from '@payloadcms/ui/shared' import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig' import { getClientSchemaMap } from '@payloadcms/ui/utilities/getClientSchemaMap' import { getSchemaMap } from '@payloadcms/ui/utilities/getSchemaMap' import { notFound } from 'next/navigation.js' import React from 'react' -import { getLatestVersion } from '../Versions/getLatestVersion.js' +import type { CompareOption } from './Default/types.js' + import { DefaultVersionView } from './Default/index.js' +import { fetchLatestVersion, fetchVersion, fetchVersions } from './fetchVersions.js' import { RenderDiff } from './RenderFieldsToDiff/index.js' +import { getVersionLabel } from './VersionPillLabel/getVersionLabel.js' +import { VersionPillLabel } from './VersionPillLabel/VersionPillLabel.js' export async function VersionView(props: DocumentViewServerProps) { - const { i18n, initPageResult, routeSegments, searchParams } = props + const { hasPublishedDoc, i18n, initPageResult, routeSegments, searchParams } = props const { collectionConfig, @@ -26,125 +30,159 @@ export async function VersionView(props: DocumentViewServerProps) { globalConfig, permissions, req, - req: { payload, payload: { config } = {}, user } = {}, + req: { payload, payload: { config, config: { localization } } = {}, user } = {}, } = initPageResult - const versionID = routeSegments[routeSegments.length - 1] + const versionToID = routeSegments[routeSegments.length - 1] const collectionSlug = collectionConfig?.slug const globalSlug = globalConfig?.slug + const draftsEnabled = (collectionConfig ?? globalConfig)?.versions?.drafts + const localeCodesFromParams = searchParams.localeCodes ? JSON.parse(searchParams.localeCodes as string) : null - const comparisonVersionIDFromParams: string = searchParams.compareValue as string + const versionFromIDFromParams = searchParams.versionFrom as string const modifiedOnly: boolean = searchParams.modifiedOnly === 'false' ? false : true - const { localization } = config + const docPermissions: SanitizedCollectionPermission | SanitizedGlobalPermission = collectionSlug + ? permissions.collections[collectionSlug] + : permissions.globals[globalSlug] - let docPermissions: SanitizedCollectionPermission | SanitizedGlobalPermission - let slug: string + const versionTo = await fetchVersion<{ + _status?: string + }>({ + id: versionToID, + collectionSlug, + depth: 1, + globalSlug, + locale: 'all', + overrideAccess: false, + req, + user, + }) - let doc: Document - let latestPublishedVersion = null - let latestDraftVersion = null + if (!versionTo) { + return notFound() + } - if (collectionSlug) { - // /collections/:slug/:id/versions/:versionID - slug = collectionSlug - docPermissions = permissions.collections[collectionSlug] - - try { - doc = await payload.findVersionByID({ - id: versionID, - collection: slug, - depth: 0, - locale: 'all', - overrideAccess: false, - req, - user, - }) - - if (collectionConfig?.versions?.drafts) { - latestDraftVersion = await getLatestVersion({ - slug, - type: 'collection', + const [ + previousVersionResult, + versionFromResult, + currentlyPublishedVersion, + latestDraftVersion, + previousPublishedVersionResult, + ] = await Promise.all([ + // Previous version (the one before the versionTo) + fetchVersions({ + collectionSlug, + // If versionFromIDFromParams is provided, the previous version is only used in the version comparison dropdown => depth 0 is enough. + // If it's not provided, this is used as `versionFrom` in the comparison, which expects populated data => depth 1 is needed. + depth: versionFromIDFromParams ? 0 : 1, + draft: true, + globalSlug, + limit: 1, + locale: 'all', + overrideAccess: false, + parentID: id, + req, + sort: '-updatedAt', + user, + where: { + and: [ + { + updatedAt: { + less_than: versionTo.updatedAt, + }, + }, + ], + }, + }), + // Version from ID from params + (versionFromIDFromParams + ? fetchVersion({ + id: versionFromIDFromParams, + collectionSlug, + depth: 1, + globalSlug, + locale: 'all', + overrideAccess: false, + req, + user, + }) + : Promise.resolve(null)) as Promise>, + // Currently published version - do note: currently published != latest published, as an unpublished version can be the latest published + hasPublishedDoc + ? fetchLatestVersion({ + collectionSlug, + depth: 0, + globalSlug, locale: 'all', overrideAccess: false, parentID: id, - payload, req, - status: 'draft', + status: 'published', + user, }) - latestPublishedVersion = await getLatestVersion({ - slug, - type: 'collection', + : Promise.resolve(null), + // Latest draft version + draftsEnabled + ? fetchLatestVersion({ + collectionSlug, + depth: 0, + globalSlug, locale: 'all', overrideAccess: false, parentID: id, - payload, - req, - status: 'published', - }) - } - } catch (_err) { - return notFound() - } - } - - if (globalSlug) { - // /globals/:slug/versions/:versionID - slug = globalSlug - docPermissions = permissions.globals[globalSlug] - - try { - doc = await payload.findGlobalVersionByID({ - id: versionID, - slug, - depth: 0, - locale: 'all', - overrideAccess: false, - req, - user, - }) - - if (globalConfig?.versions?.drafts) { - latestDraftVersion = await getLatestVersion({ - slug, - type: 'global', - locale: 'all', - overrideAccess: false, - payload, req, status: 'draft', + user, }) - latestPublishedVersion = await getLatestVersion({ - slug, - type: 'global', - locale: 'all', - overrideAccess: false, - payload, - req, - status: 'published', - }) - } - } catch (_err) { - return notFound() - } - } + : Promise.resolve(null), + // Previous published version + fetchVersions({ + collectionSlug, + depth: 0, + draft: true, + globalSlug, + limit: 1, + locale: 'all', + overrideAccess: false, + parentID: id, + req, + sort: '-updatedAt', + user, + where: { + and: [ + { + updatedAt: { + less_than: versionTo.updatedAt, + }, + }, + { + 'version._status': { + equals: 'published', + }, + }, + ], + }, + }), + ]) - const publishedNewerThanDraft = latestPublishedVersion?.updatedAt > latestDraftVersion?.updatedAt + const previousVersion: null | TypeWithVersion = previousVersionResult?.docs?.[0] ?? null - if (publishedNewerThanDraft) { - latestDraftVersion = { - id: '', - updatedAt: '', - } - } + const versionFrom = + versionFromResult || + // By default, we'll compare the previous version. => versionFrom = version previous to versionTo + previousVersion - let selectedLocales: OptionObject[] = [] + // Previous published version before the versionTo + const previousPublishedVersion = previousPublishedVersionResult?.docs?.[0] ?? null + + let selectedLocales: string[] = [] if (localization) { let locales: Locale[] = [] if (localeCodesFromParams) { @@ -163,48 +201,7 @@ export async function VersionView(props: DocumentViewServerProps) { locales = (await localization.filterAvailableLocales({ locales, req })) || [] } - selectedLocales = locales.map((locale) => ({ - label: locale.label, - value: locale.code, - })) - } - - const latestVersion = - latestPublishedVersion?.updatedAt > latestDraftVersion?.updatedAt - ? latestPublishedVersion - : latestDraftVersion - - if (!doc) { - return notFound() - } - - /** - * The doc to compare this version to is either the latest version, or a specific version if specified in the URL. - * This specific version is added to the URL when a user selects a version to compare to. - */ - let comparisonDoc = null - if (comparisonVersionIDFromParams) { - if (collectionSlug) { - comparisonDoc = await payload.findVersionByID({ - id: comparisonVersionIDFromParams, - collection: collectionSlug, - depth: 0, - locale: 'all', - overrideAccess: false, - req, - }) - } else { - comparisonDoc = await payload.findGlobalVersionByID({ - id: comparisonVersionIDFromParams, - slug: globalSlug, - depth: 0, - locale: 'all', - overrideAccess: false, - req, - }) - } - } else { - comparisonDoc = latestVersion + selectedLocales = locales.map((locale) => locale.code) } const schemaMap = getSchemaMap({ @@ -222,10 +219,8 @@ export async function VersionView(props: DocumentViewServerProps) { payload, schemaMap, }) - const RenderedDiff = RenderDiff({ clientSchemaMap, - comparisonSiblingData: comparisonDoc?.version, customDiffComponents: {}, entitySlug: collectionSlug || globalSlug, fieldPermissions: docPermissions?.fields, @@ -237,26 +232,200 @@ export async function VersionView(props: DocumentViewServerProps) { parentPath: '', parentSchemaPath: '', req, - selectedLocales: selectedLocales && selectedLocales.map((locale) => locale.value), - versionSiblingData: globalConfig - ? { - ...doc?.version, - createdAt: doc?.version?.createdAt || doc.createdAt, - updatedAt: doc?.version?.updatedAt || doc.updatedAt, - } - : doc?.version, + selectedLocales, + versionFromSiblingData: { + ...versionFrom?.version, + updatedAt: versionFrom?.updatedAt, + }, + versionToSiblingData: { + ...versionTo.version, + updatedAt: versionTo.updatedAt, + }, }) + const versionToCreatedAtFormatted = versionTo.updatedAt + ? formatDate({ + date: + typeof versionTo.updatedAt === 'string' + ? new Date(versionTo.updatedAt) + : (versionTo.updatedAt as Date), + i18n, + pattern: config.admin.dateFormat, + }) + : '' + + const formatPill = ({ + doc, + labelOverride, + labelStyle, + labelSuffix, + }: { + doc: TypeWithVersion + labelOverride?: string + labelStyle?: 'pill' | 'text' + labelSuffix?: React.ReactNode + }): React.ReactNode => { + return ( + + ) + } + + // SelectComparison Options: + // + // Previous version: always, unless doesn't exist. Can be the same as previously published + // Latest draft: only if no newer published exists (latestDraftVersion) + // Currently published: always, if exists + // Previously published: if there is a prior published version older than versionTo + // Specific Version: only if not already present under other label (= versionFrom) + + let versionFromOptions: { + doc: TypeWithVersion + labelOverride?: string + updatedAt: Date + value: string + }[] = [] + + // Previous version + if (previousVersion?.id) { + versionFromOptions.push({ + doc: previousVersion, + labelOverride: i18n.t('version:previousVersion'), + updatedAt: new Date(previousVersion.updatedAt), + value: previousVersion.id, + }) + } + + // Latest Draft + const publishedNewerThanDraft = + currentlyPublishedVersion?.updatedAt > latestDraftVersion?.updatedAt + if (latestDraftVersion && !publishedNewerThanDraft) { + versionFromOptions.push({ + doc: latestDraftVersion, + updatedAt: new Date(latestDraftVersion.updatedAt), + value: latestDraftVersion.id, + }) + } + + // Currently Published + if (currentlyPublishedVersion) { + versionFromOptions.push({ + doc: currentlyPublishedVersion, + updatedAt: new Date(currentlyPublishedVersion.updatedAt), + value: currentlyPublishedVersion.id, + }) + } + + // Previous Published + if (previousPublishedVersion && currentlyPublishedVersion?.id !== previousPublishedVersion.id) { + versionFromOptions.push({ + doc: previousPublishedVersion, + labelOverride: i18n.t('version:previouslyPublished'), + updatedAt: new Date(previousPublishedVersion.updatedAt), + value: previousPublishedVersion.id, + }) + } + + // Specific Version + if (versionFrom?.id && !versionFromOptions.some((option) => option.value === versionFrom.id)) { + // Only add "specific version" if it is not already in the options + versionFromOptions.push({ + doc: versionFrom, + labelOverride: i18n.t('version:specificVersion'), + updatedAt: new Date(versionFrom.updatedAt), + value: versionFrom.id, + }) + } + + versionFromOptions = versionFromOptions.sort((a, b) => { + // Sort by updatedAt, newest first + if (a && b) { + return b.updatedAt.getTime() - a.updatedAt.getTime() + } + return 0 + }) + + const versionToIsVersionFrom = versionFrom?.id === versionTo.id + + const versionFromComparisonOptions: CompareOption[] = [] + + for (const option of versionFromOptions) { + const isVersionTo = option.value === versionTo.id + + if (isVersionTo && !versionToIsVersionFrom) { + // Don't offer selecting a versionFrom that is the same as versionTo, unless it's already selected + continue + } + + const alreadyAdded = versionFromComparisonOptions.some( + (existingOption) => existingOption.value === option.value, + ) + if (alreadyAdded) { + continue + } + + const otherOptionsWithSameID = versionFromOptions.filter( + (existingOption) => existingOption.value === option.value && existingOption !== option, + ) + + // Merge options with same ID to the same option + const labelSuffix = otherOptionsWithSameID?.length ? ( + + {' ('} + {otherOptionsWithSameID.map((optionWithSameID, index) => { + const label = + optionWithSameID.labelOverride || + getVersionLabel({ + currentlyPublishedVersion, + latestDraftVersion, + t: i18n.t, + version: optionWithSameID.doc, + }).label + + return ( + + {index > 0 ? ', ' : ''} + {label} + + ) + })} + {')'} + + ) : undefined + + versionFromComparisonOptions.push({ + label: formatPill({ + doc: option.doc, + labelOverride: option.labelOverride, + labelSuffix, + }), + value: option.value, + }) + } + return ( ) } diff --git a/packages/next/src/views/Version/metadata.ts b/packages/next/src/views/Version/metadata.ts index fae94310f..7df519074 100644 --- a/packages/next/src/views/Version/metadata.ts +++ b/packages/next/src/views/Version/metadata.ts @@ -36,7 +36,7 @@ export const generateVersionViewMetadata: GenerateEditViewMetadata = async ({ metaToUse = { ...(config.admin.meta || {}), - description: t('version:viewingVersion', { documentTitle: doc[useAsTitle], entityLabel }), + description: t('version:viewingVersion', { documentTitle: titleFromData, entityLabel }), title: `${t('version:version')}${formattedCreatedAt ? ` - ${formattedCreatedAt}` : ''}${titleFromData ? ` - ${titleFromData}` : ''} - ${entityLabel}`, ...(collectionConfig?.admin?.meta || {}), ...(collectionConfig?.admin?.components?.views?.edit?.version?.meta || {}), diff --git a/packages/next/src/views/Versions/buildColumns.tsx b/packages/next/src/views/Versions/buildColumns.tsx index 1e1a70380..aebbf7b6e 100644 --- a/packages/next/src/views/Versions/buildColumns.tsx +++ b/packages/next/src/views/Versions/buildColumns.tsx @@ -12,29 +12,38 @@ import { SortColumn } from '@payloadcms/ui' import React from 'react' import { AutosaveCell } from './cells/AutosaveCell/index.js' -import { CreatedAtCell } from './cells/CreatedAt/index.js' +import { CreatedAtCell, type CreatedAtCellProps } from './cells/CreatedAt/index.js' import { IDCell } from './cells/ID/index.js' export const buildVersionColumns = ({ collectionConfig, + CreatedAtCellOverride, + currentlyPublishedVersion, docID, docs, globalConfig, i18n: { t }, latestDraftVersion, - latestPublishedVersion, }: { collectionConfig?: SanitizedCollectionConfig - config: SanitizedConfig + CreatedAtCellOverride?: React.ComponentType + currentlyPublishedVersion?: { + id: number | string + updatedAt: string + } docID?: number | string docs: PaginatedDocs>['docs'] globalConfig?: SanitizedGlobalConfig i18n: I18n - latestDraftVersion?: string - latestPublishedVersion?: string + latestDraftVersion?: { + id: number | string + updatedAt: string + } }): Column[] => { const entityConfig = collectionConfig || globalConfig + const CreatedAtCellComponent = CreatedAtCellOverride ?? CreatedAtCell + const columns: Column[] = [ { accessor: 'updatedAt', @@ -46,7 +55,7 @@ export const buildVersionColumns = ({ Heading: , renderedCells: docs.map((doc, i) => { return ( - { return ( ) diff --git a/packages/next/src/views/Versions/cells/AutosaveCell/index.tsx b/packages/next/src/views/Versions/cells/AutosaveCell/index.tsx index ccc3bef26..0939ee132 100644 --- a/packages/next/src/views/Versions/cells/AutosaveCell/index.tsx +++ b/packages/next/src/views/Versions/cells/AutosaveCell/index.tsx @@ -1,85 +1,49 @@ 'use client' -import { Pill, useConfig, useTranslation } from '@payloadcms/ui' +import { Pill, useTranslation } from '@payloadcms/ui' import React from 'react' import './index.scss' +import { VersionPillLabel } from '../../../Version/VersionPillLabel/VersionPillLabel.js' const baseClass = 'autosave-cell' type AutosaveCellProps = { - latestDraftVersion?: string - latestPublishedVersion?: string - rowData?: { + currentlyPublishedVersion?: { + id: number | string + updatedAt: string + } + latestDraftVersion?: { + id: number | string + updatedAt: string + } + rowData: { autosave?: boolean + id: number | string publishedLocale?: string version: { - _status?: string + _status: string } } } -export const renderPill = (data, latestVersion, currentLabel, previousLabel, pillStyle) => { - return ( - - {data?.id === latestVersion ? ( - - {currentLabel} - - ) : ( - {previousLabel} - )} - - ) -} - export const AutosaveCell: React.FC = ({ + currentlyPublishedVersion, latestDraftVersion, - latestPublishedVersion, - rowData = { autosave: undefined, publishedLocale: undefined, version: undefined }, + rowData, }) => { - const { i18n, t } = useTranslation() - - const { - config: { localization }, - } = useConfig() - - const publishedLocale = rowData?.publishedLocale || undefined - const status = rowData?.version._status - let publishedLocalePill = null - - const versionInfo = { - draft: { - currentLabel: t('version:currentDraft'), - latestVersion: latestDraftVersion, - pillStyle: undefined, - previousLabel: t('version:draft'), - }, - published: { - currentLabel: t('version:currentPublishedVersion'), - latestVersion: latestPublishedVersion, - pillStyle: 'success', - previousLabel: t('version:previouslyPublished'), - }, - } - - const { currentLabel, latestVersion, pillStyle, previousLabel } = versionInfo[status] || {} - - if (localization && localization?.locales && publishedLocale) { - const localeCode = Array.isArray(publishedLocale) ? publishedLocale[0] : publishedLocale - - const locale = localization.locales.find((loc) => loc.code === localeCode) - const formattedLabel = locale?.label?.[i18n?.language] || locale?.label - - if (formattedLabel) { - publishedLocalePill = {formattedLabel} - } - } + const { t } = useTranslation() return (
- {rowData?.autosave && {t('version:autosave')}} - {status && renderPill(rowData, latestVersion, currentLabel, previousLabel, pillStyle)} - {publishedLocalePill} + {rowData?.autosave && {t('version:autosave')}} +
) } diff --git a/packages/next/src/views/Versions/cells/CreatedAt/index.tsx b/packages/next/src/views/Versions/cells/CreatedAt/index.tsx index ce245fb2b..c75233a1a 100644 --- a/packages/next/src/views/Versions/cells/CreatedAt/index.tsx +++ b/packages/next/src/views/Versions/cells/CreatedAt/index.tsx @@ -4,7 +4,7 @@ import { formatDate } from '@payloadcms/ui/shared' import { formatAdminURL } from 'payload/shared' import React from 'react' -type CreatedAtCellProps = { +export type CreatedAtCellProps = { collectionSlug?: string docID?: number | string globalSlug?: string diff --git a/packages/next/src/views/Versions/getLatestVersion.ts b/packages/next/src/views/Versions/getLatestVersion.ts deleted file mode 100644 index 0c8ca066f..000000000 --- a/packages/next/src/views/Versions/getLatestVersion.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type { Payload, PayloadRequest, Where } from 'payload' - -import { logError } from 'payload' - -type ReturnType = { - id: string - updatedAt: string -} | null - -type Args = { - locale?: string - overrideAccess?: boolean - parentID?: number | string - payload: Payload - req?: PayloadRequest - slug: string - status: 'draft' | 'published' - type: 'collection' | 'global' -} -export async function getLatestVersion(args: Args): Promise { - const { slug, type = 'collection', locale, overrideAccess, parentID, payload, req, status } = args - - const and: Where[] = [ - { - 'version._status': { - equals: status, - }, - }, - ] - - if (type === 'collection' && parentID) { - and.push({ - parent: { - equals: parentID, - }, - }) - } - - try { - const sharedOptions = { - depth: 0, - limit: 1, - locale, - overrideAccess, - req, - sort: '-updatedAt', - where: { - and, - }, - } - - const response = - type === 'collection' - ? await payload.findVersions({ - collection: slug, - ...sharedOptions, - }) - : await payload.findGlobalVersions({ - slug, - ...sharedOptions, - }) - - if (!response.docs.length) { - return null - } - - return { - id: response.docs[0].id, - updatedAt: response.docs[0].updatedAt, - } - } catch (err) { - logError({ err, payload }) - - return null - } -} diff --git a/packages/next/src/views/Versions/index.tsx b/packages/next/src/views/Versions/index.tsx index 744ddfd08..c4a564cf4 100644 --- a/packages/next/src/views/Versions/index.tsx +++ b/packages/next/src/views/Versions/index.tsx @@ -1,36 +1,40 @@ import { Gutter, ListQueryProvider, SetDocumentStepNav } from '@payloadcms/ui' import { notFound } from 'next/navigation.js' -import { type DocumentViewServerProps, logError, type PaginatedDocs } from 'payload' +import { type DocumentViewServerProps, type PaginatedDocs, type Where } from 'payload' import { isNumber } from 'payload/shared' import React from 'react' +import { fetchLatestVersion, fetchVersions } from '../Version/fetchVersions.js' +import { VersionDrawerCreatedAtCell } from '../Version/SelectComparison/VersionDrawer/CreatedAtCell.js' import { buildVersionColumns } from './buildColumns.js' -import { getLatestVersion } from './getLatestVersion.js' import { VersionsViewClient } from './index.client.js' import './index.scss' -export const baseClass = 'versions' +const baseClass = 'versions' export async function VersionsView(props: DocumentViewServerProps) { - const { initPageResult, searchParams } = props - const { - collectionConfig, - docID: id, - globalConfig, - req, - req: { - i18n, - payload, - payload: { config }, - t, - user, + hasPublishedDoc, + initPageResult: { + collectionConfig, + docID: id, + globalConfig, + req, + req: { + i18n, + payload: { config }, + t, + user, + }, }, - } = initPageResult + searchParams: { limit, page, sort }, + versions: { disableGutter = false, useVersionDrawerCreatedAtCell = false } = {}, + } = props + + const draftsEnabled = (collectionConfig ?? globalConfig)?.versions?.drafts const collectionSlug = collectionConfig?.slug const globalSlug = globalConfig?.slug - const { limit, page, sort } = searchParams const { localization, @@ -38,177 +42,110 @@ export async function VersionsView(props: DocumentViewServerProps) { serverURL, } = config - let versionsData: PaginatedDocs - let limitToUse = isNumber(limit) ? Number(limit) : undefined - let latestPublishedVersion = null - let latestDraftVersion = null + const whereQuery: { + and: Array<{ parent?: { equals: number | string }; snapshot?: { not_equals: boolean } }> + } & Where = { + and: [], + } + if (localization && draftsEnabled) { + whereQuery.and.push({ + snapshot: { + not_equals: true, + }, + }) + } - if (collectionSlug) { - limitToUse = limitToUse || collectionConfig.admin.pagination.defaultLimit - const whereQuery: { - and: Array<{ parent?: { equals: number | string }; snapshot?: { not_equals: boolean } }> - } = { - and: [ - { - parent: { - equals: id, - }, - }, - ], - } + const defaultLimit = collectionSlug ? collectionConfig?.admin?.pagination?.defaultLimit : 10 - if (localization && collectionConfig?.versions?.drafts) { - whereQuery.and.push({ - snapshot: { - not_equals: true, - }, - }) - } + const limitToUse = isNumber(limit) ? Number(limit) : defaultLimit - try { - versionsData = await payload.findVersions({ - collection: collectionSlug, - depth: 0, - limit: limitToUse, - overrideAccess: false, - page: page ? parseInt(page.toString(), 10) : undefined, - req, - sort: sort as string, - user, - where: whereQuery, - }) - if (collectionConfig?.versions?.drafts) { - latestDraftVersion = await getLatestVersion({ - slug: collectionSlug, - type: 'collection', - parentID: id, - payload, - status: 'draft', - }) - const publishedDoc = await payload.count({ - collection: collectionSlug, + const versionsData: PaginatedDocs = await fetchVersions({ + collectionSlug, + depth: 0, + globalSlug, + limit: limitToUse, + overrideAccess: false, + page: page ? parseInt(page.toString(), 10) : undefined, + parentID: id, + req, + sort: sort as string, + user, + where: whereQuery, + }) + + if (!versionsData) { + return notFound() + } + + const [currentlyPublishedVersion, latestDraftVersion] = await Promise.all([ + hasPublishedDoc + ? fetchLatestVersion({ + collectionSlug, depth: 0, - overrideAccess: true, + globalSlug, + overrideAccess: false, + parentID: id, req, - where: { - id: { - equals: id, - }, - _status: { - equals: 'published', - }, + select: { + id: true, + updatedAt: true, }, - }) - - // If we pass a latestPublishedVersion to buildVersionColumns, - // this will be used to display it as the "current published version". - // However, the latest published version might have been unpublished in the meantime. - // Hence, we should only pass the latest published version if there is a published document. - latestPublishedVersion = - publishedDoc.totalDocs > 0 && - (await getLatestVersion({ - slug: collectionSlug, - type: 'collection', - parentID: id, - payload, - status: 'published', - })) - } - } catch (err) { - logError({ err, payload }) - } - } - - if (globalSlug) { - limitToUse = limitToUse || 10 - const whereQuery = - localization && globalConfig?.versions?.drafts - ? { - snapshot: { - not_equals: true, - }, - } - : {} - - try { - versionsData = await payload.findGlobalVersions({ - slug: globalSlug, - depth: 0, - limit: limitToUse, - overrideAccess: false, - page: page ? parseInt(page as string, 10) : undefined, - req, - sort: sort as string, - user, - where: whereQuery, - }) - - if (globalConfig?.versions?.drafts) { - latestDraftVersion = await getLatestVersion({ - slug: globalSlug, - type: 'global', - payload, - status: 'draft', - }) - latestPublishedVersion = await getLatestVersion({ - slug: globalSlug, - type: 'global', - payload, status: 'published', + user, }) - } - } catch (err) { - logError({ err, payload }) - } + : Promise.resolve(null), + draftsEnabled + ? fetchLatestVersion({ + collectionSlug, + depth: 0, + globalSlug, + overrideAccess: false, + parentID: id, + req, + select: { + id: true, + updatedAt: true, + }, + status: 'draft', + user, + }) + : Promise.resolve(null), + ]) - if (!versionsData) { - return notFound() - } - } const fetchURL = collectionSlug ? `${serverURL}${apiRoute}/${collectionSlug}/versions` - : globalSlug - ? `${serverURL}${apiRoute}/globals/${globalSlug}/versions` - : '' - - const publishedNewerThanDraft = latestPublishedVersion?.updatedAt > latestDraftVersion?.updatedAt - - if (publishedNewerThanDraft) { - latestDraftVersion = { - id: '', - updatedAt: '', - } - } + : `${serverURL}${apiRoute}/globals/${globalSlug}/versions` const columns = buildVersionColumns({ collectionConfig, - config, + CreatedAtCellOverride: useVersionDrawerCreatedAtCell ? VersionDrawerCreatedAtCell : undefined, + currentlyPublishedVersion, docID: id, docs: versionsData?.docs, globalConfig, i18n, - latestDraftVersion: latestDraftVersion?.id, - latestPublishedVersion: latestPublishedVersion?.id, + latestDraftVersion, }) - const pluralLabel = collectionConfig?.labels?.plural - ? typeof collectionConfig.labels.plural === 'function' + const pluralLabel = + typeof collectionConfig?.labels?.plural === 'function' ? collectionConfig.labels.plural({ i18n, t }) - : collectionConfig.labels.plural - : globalConfig?.label + : (collectionConfig?.labels?.plural ?? globalConfig?.label) + + const GutterComponent = disableGutter ? React.Fragment : Gutter return (
- + - +
) diff --git a/packages/payload/src/admin/forms/Diff.ts b/packages/payload/src/admin/forms/Diff.ts index 340f4c8ad..81be9ff08 100644 --- a/packages/payload/src/admin/forms/Diff.ts +++ b/packages/payload/src/admin/forms/Diff.ts @@ -29,6 +29,8 @@ export type VersionField = { /** * Taken from react-diff-viewer-continued + * + * @deprecated remove in 4.0 - react-diff-viewer-continued is no longer a dependency */ export declare enum DiffMethod { CHARS = 'diffChars', @@ -44,10 +46,13 @@ export declare enum DiffMethod { export type FieldDiffClientProps = { baseVersionField: BaseVersionField /** - * Field value from the version being compared + * Field value from the version being compared from */ - comparisonValue: unknown - diffMethod: DiffMethod + comparisonValue: unknown // TODO: change to valueFrom in 4.0 + /** + * @deprecated remove in 4.0. react-diff-viewer-continued is no longer a dependency + */ + diffMethod: any field: TClientField fieldPermissions: | { @@ -58,11 +63,13 @@ export type FieldDiffClientProps Promise | unknown -export type ServerFunction = ( - args: DefaultServerFunctionArgs & ServerFunctionClientArgs['args'], -) => Promise | unknown +export type ServerFunction< + TArgs extends object = Record, + TReturnType = Promise | unknown, +> = (args: DefaultServerFunctionArgs & TArgs) => TReturnType export type ServerFunctionConfig = { fn: ServerFunction diff --git a/packages/payload/src/admin/types.ts b/packages/payload/src/admin/types.ts index 42ebef54e..ff3792db1 100644 --- a/packages/payload/src/admin/types.ts +++ b/packages/payload/src/admin/types.ts @@ -625,6 +625,7 @@ export type { DocumentViewServerProps, DocumentViewServerPropsOnly, EditViewProps, + RenderDocumentVersionsProperties, } from './views/document.js' export type { diff --git a/packages/payload/src/admin/views/document.ts b/packages/payload/src/admin/views/document.ts index 3b83357f4..abcdeefd1 100644 --- a/packages/payload/src/admin/views/document.ts +++ b/packages/payload/src/admin/views/document.ts @@ -9,11 +9,27 @@ export type EditViewProps = { readonly collectionSlug?: string readonly globalSlug?: string } +/** + * Properties specific to the versions view + */ +export type RenderDocumentVersionsProperties = { + /** + * @default false + */ + disableGutter?: boolean + /** + * Use createdAt cell that appends params to the url on version selection instead of redirecting user + * @default false + */ + useVersionDrawerCreatedAtCell?: boolean +} export type DocumentViewServerPropsOnly = { - readonly doc: Data - readonly initPageResult: InitPageResult - readonly routeSegments: string[] + doc: Data + hasPublishedDoc: boolean + initPageResult: InitPageResult + routeSegments: string[] + versions?: RenderDocumentVersionsProperties } & ServerProps export type DocumentViewServerProps = DocumentViewClientProps & DocumentViewServerPropsOnly diff --git a/packages/richtext-lexical/src/field/Diff/converters/listitem/index.scss b/packages/richtext-lexical/src/field/Diff/converters/listitem/index.scss index 4badac295..d275ab163 100644 --- a/packages/richtext-lexical/src/field/Diff/converters/listitem/index.scss +++ b/packages/richtext-lexical/src/field/Diff/converters/listitem/index.scss @@ -1,5 +1,4 @@ @import '~@payloadcms/ui/scss'; -@import '../../colors.scss'; @layer payload-default { .lexical-diff { diff --git a/packages/richtext-lexical/src/field/Diff/converters/listitem/index.tsx b/packages/richtext-lexical/src/field/Diff/converters/listitem/index.tsx index 195bb7bae..3b769241d 100644 --- a/packages/richtext-lexical/src/field/Diff/converters/listitem/index.tsx +++ b/packages/richtext-lexical/src/field/Diff/converters/listitem/index.tsx @@ -48,7 +48,7 @@ export const ListItemDiffHTMLConverterAsync: HTMLConvertersAsync ) - const html = ReactDOMServer.renderToString(JSX) + const html = ReactDOMServer.renderToStaticMarkup(JSX) // Add style="list-style-type: none;${providedCSSString}" to html const styleIndex = html.indexOf('class="list-item-checkbox') diff --git a/packages/richtext-lexical/src/field/Diff/converters/relationship/index.scss b/packages/richtext-lexical/src/field/Diff/converters/relationship/index.scss index e728466ac..69fdf00c1 100644 --- a/packages/richtext-lexical/src/field/Diff/converters/relationship/index.scss +++ b/packages/richtext-lexical/src/field/Diff/converters/relationship/index.scss @@ -1,79 +1,73 @@ @import '~@payloadcms/ui/scss'; -@import '../../colors.scss'; @layer payload-default { - .lexical-diff__diff-container { - .lexical-relationship-diff { - @extend %body; - @include shadow-sm; - min-width: calc(var(--base) * 8); - max-width: fit-content; + .lexical-diff .lexical-relationship-diff { + @extend %body; + @include shadow-sm; + min-width: calc(var(--base) * 8); + max-width: fit-content; - display: flex; - align-items: center; - background-color: var(--theme-input-bg); - border-radius: $style-radius-s; - border: 1px solid var(--theme-elevation-100); - position: relative; - font-family: var(--font-body); - margin-block: base(0.5); - max-height: calc(var(--base) * 4); - padding: base(0.6); + display: flex; + align-items: center; + background-color: var(--theme-input-bg); + border-radius: $style-radius-s; + border: 1px solid var(--theme-elevation-100); + position: relative; + font-family: var(--font-body); + margin-block: base(0.5); + max-height: calc(var(--base) * 4); + padding: base(0.6); - &[data-match-type='create'] { - border-color: var(--diff-create-pill-border); + &[data-match-type='create'] { + border-color: var(--diff-create-pill-border); + color: var(--diff-create-parent-color); + + .lexical-relationship-diff__collectionLabel { + color: var(--diff-create-link-color); + } + + .lexical-relationship-diff__title * { color: var(--diff-create-parent-color); + } + } - .lexical-relationship-diff__collectionLabel { - color: var(--diff-create-link-color); - } + &[data-match-type='delete'] { + border-color: var(--diff-delete-pill-border); + color: var(--diff-delete-parent-color); + text-decoration-line: none; + background-color: var(--diff-delete-pill-bg); - [data-match-type='create'] { - color: var(--diff-create-parent-color); - } + .lexical-relationship-diff__collectionLabel { + color: var(--diff-delete-link-color); } - &[data-match-type='delete'] { - border-color: var(--diff-delete-pill-border); - color: var(--diff-delete-parent-color); + * { text-decoration-line: none; - background-color: var(--diff-delete-pill-bg); - - .lexical-relationship-diff__collectionLabel { - color: var(--diff-delete-link-color); - } - - [data-match-type='delete'] { - text-decoration-line: none; - } - - * { - color: var(--diff-delete-parent-color); - } + color: var(--diff-delete-parent-color); } + } - &__card { - display: flex; - flex-direction: column; - width: 100%; - flex-grow: 1; - display: flex; - align-items: flex-start; - flex-direction: column; - justify-content: space-between; - } + &__card { + display: flex; + flex-direction: column; + width: 100%; + flex-grow: 1; + display: flex; + align-items: flex-start; + flex-direction: column; + justify-content: space-between; + } - &__title { - display: flex; - flex-direction: row; - font-weight: 600; - } + &__title { + display: flex; + flex-direction: row; + font-weight: 600; + } - &__collectionLabel { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } + &__collectionLabel { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } } } diff --git a/packages/richtext-lexical/src/field/Diff/converters/relationship/index.tsx b/packages/richtext-lexical/src/field/Diff/converters/relationship/index.tsx index 28859d309..de5316ae6 100644 --- a/packages/richtext-lexical/src/field/Diff/converters/relationship/index.tsx +++ b/packages/richtext-lexical/src/field/Diff/converters/relationship/index.tsx @@ -4,6 +4,8 @@ import { getTranslation, type I18nClient } from '@payloadcms/translations' import './index.scss' +import { formatAdminURL } from 'payload/shared' + import type { HTMLConvertersAsync } from '../../../../features/converters/lexicalToHtml/async/types.js' import type { SerializedRelationshipNode } from '../../../../nodeTypes.js' @@ -17,13 +19,15 @@ export const RelationshipDiffHTMLConverterAsync: (args: { relationship: async ({ node, populate, providedCSSString }) => { let data: (Record & TypeWithID) | undefined = undefined + const id = typeof node.value === 'object' ? node.value.id : node.value + // If there's no valid upload data, populate return an empty string if (typeof node.value !== 'object') { if (!populate) { return '' } data = await populate({ - id: node.value, + id, collectionSlug: node.relationTo, }) } else { @@ -38,7 +42,7 @@ export const RelationshipDiffHTMLConverterAsync: (args: {
@@ -56,7 +60,11 @@ export const RelationshipDiffHTMLConverterAsync: (args: { @@ -64,14 +72,14 @@ export const RelationshipDiffHTMLConverterAsync: (args: { ) : ( - {node.value as string} + {id as string} )}
) // Render to HTML - const html = ReactDOMServer.renderToString(JSX) + const html = ReactDOMServer.renderToStaticMarkup(JSX) return html }, diff --git a/packages/richtext-lexical/src/field/Diff/converters/unknown/index.scss b/packages/richtext-lexical/src/field/Diff/converters/unknown/index.scss index 514480b9b..8875820e4 100644 --- a/packages/richtext-lexical/src/field/Diff/converters/unknown/index.scss +++ b/packages/richtext-lexical/src/field/Diff/converters/unknown/index.scss @@ -1,42 +1,39 @@ @import '~@payloadcms/ui/scss'; -@import '../../colors.scss'; @layer payload-default { - .lexical-diff__diff-container { - .lexical-unknown-diff { - @extend %body; - @include shadow-sm; - max-width: fit-content; - display: flex; - align-items: center; - background: var(--theme-input-bg); - border-radius: $style-radius-s; - border: 1px solid var(--theme-elevation-100); - position: relative; - font-family: var(--font-body); - margin-block: base(0.5); - max-height: calc(var(--base) * 4); - padding: base(0.25); + .lexical-diff .lexical-unknown-diff { + @extend %body; + @include shadow-sm; + max-width: fit-content; + display: flex; + align-items: center; + background: var(--theme-input-bg); + border-radius: $style-radius-s; + border: 1px solid var(--theme-elevation-100); + position: relative; + font-family: var(--font-body); + margin-block: base(0.5); + max-height: calc(var(--base) * 4); + padding: base(0.25); - &__specifier { - font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; - } + &__specifier { + font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; + } - &[data-match-type='create'] { - border-color: var(--diff-create-pill-border); - color: var(--diff-create-parent-color); - } + &[data-match-type='create'] { + border-color: var(--diff-create-pill-border); + color: var(--diff-create-parent-color); + } - &[data-match-type='delete'] { - border-color: var(--diff-delete-pill-border); - color: var(--diff-delete-parent-color); + &[data-match-type='delete'] { + border-color: var(--diff-delete-pill-border); + color: var(--diff-delete-parent-color); + text-decoration-line: none; + background-color: var(--diff-delete-pill-bg); + + * { text-decoration-line: none; - background-color: var(--diff-delete-pill-bg); - - * { - text-decoration-line: none; - color: var(--diff-delete-parent-color); - } + color: var(--diff-delete-parent-color); } } } diff --git a/packages/richtext-lexical/src/field/Diff/converters/unknown/index.tsx b/packages/richtext-lexical/src/field/Diff/converters/unknown/index.tsx index f14c11a0d..5dec0290e 100644 --- a/packages/richtext-lexical/src/field/Diff/converters/unknown/index.tsx +++ b/packages/richtext-lexical/src/field/Diff/converters/unknown/index.tsx @@ -54,7 +54,7 @@ export const UnknownDiffHTMLConverterAsync: (args: { ) // Render to HTML - const html = ReactDOMServer.renderToString(JSX) + const html = ReactDOMServer.renderToStaticMarkup(JSX) return html }, diff --git a/packages/richtext-lexical/src/field/Diff/converters/upload/index.scss b/packages/richtext-lexical/src/field/Diff/converters/upload/index.scss index d11a9e7d6..9c86c3eaa 100644 --- a/packages/richtext-lexical/src/field/Diff/converters/upload/index.scss +++ b/packages/richtext-lexical/src/field/Diff/converters/upload/index.scss @@ -1,8 +1,7 @@ @import '~@payloadcms/ui/scss'; -@import '../../colors.scss'; @layer payload-default { - .lexical-diff__diff-container { + .lexical-diff { .lexical-upload-diff { @extend %body; @include shadow-sm; diff --git a/packages/richtext-lexical/src/field/Diff/converters/upload/index.tsx b/packages/richtext-lexical/src/field/Diff/converters/upload/index.tsx index 1e660cfbc..bf0e280dc 100644 --- a/packages/richtext-lexical/src/field/Diff/converters/upload/index.tsx +++ b/packages/richtext-lexical/src/field/Diff/converters/upload/index.tsx @@ -72,7 +72,7 @@ export const UploadDiffHTMLConverterAsync: (args: { )} -
+
{uploadDoc?.filename}
{formatFilesize(uploadDoc?.filesize)} @@ -95,7 +95,7 @@ export const UploadDiffHTMLConverterAsync: (args: { ) // Render to HTML - const html = ReactDOMServer.renderToString(JSX) + const html = ReactDOMServer.renderToStaticMarkup(JSX) return html }, diff --git a/packages/richtext-lexical/src/field/Diff/htmlDiff/index.scss b/packages/richtext-lexical/src/field/Diff/htmlDiff/index.scss deleted file mode 100644 index 66ae4f817..000000000 --- a/packages/richtext-lexical/src/field/Diff/htmlDiff/index.scss +++ /dev/null @@ -1,90 +0,0 @@ -@import '~@payloadcms/ui/scss'; -@import '../colors.scss'; - -@layer payload-default { - .lexical-diff__diff-container { - font-family: var(--font-serif); - font-size: base(0.8); - letter-spacing: 0.02em; - - // Apply background color to parents that have children with diffs - p, - li, - h1, - h2, - h3, - h4, - h5, - blockquote, - h6 { - &:has([data-match-type='create']) { - background-color: var(--diff-create-parent-bg); - color: var(--diff-create-parent-color); - } - - &:has([data-match-type='delete']) { - background-color: var(--diff-delete-parent-bg); - color: var(--diff-delete-parent-color); - } - } - - li::marker { - color: var(--theme-text); - } - - [data-match-type='delete'] { - color: var(--diff-delete-pill-color); - text-decoration-color: var(--diff-delete-pill-color); - text-decoration-line: line-through; - background-color: var(--diff-delete-pill-bg); - border-radius: 4px; - text-decoration-thickness: 1px; - } - - a[data-match-type='delete'] { - color: var(--diff-delete-link-color); - } - - a[data-match-type='create']:not(img) { - // :not(img) required to increase specificity - color: var(--diff-create-link-color); - } - - [data-match-type='create']:not(img) { - background-color: var(--diff-create-pill-bg); - color: var(--diff-create-pill-color); - border-radius: 4px; - } - - .html-diff { - &-create-inline-wrapper, - &-delete-inline-wrapper { - display: inline-flex; - } - - &-create-block-wrapper, - &-delete-block-wrapper { - display: flex; - } - - &-create-inline-wrapper, - &-delete-inline-wrapper, - &-create-block-wrapper, - &-delete-block-wrapper { - position: relative; - align-items: center; - flex-direction: row; - - &::after { - position: absolute; - top: 0; - left: 0; - display: block; - width: 100%; - height: 100%; - content: ''; - } - } - } - } -} diff --git a/packages/richtext-lexical/src/field/Diff/index.scss b/packages/richtext-lexical/src/field/Diff/index.scss index 0e890f246..39a68ae55 100644 --- a/packages/richtext-lexical/src/field/Diff/index.scss +++ b/packages/richtext-lexical/src/field/Diff/index.scss @@ -1,30 +1,37 @@ @import '~@payloadcms/ui/scss'; -@import './colors.scss'; @layer payload-default { - .lexical-diff { - &__diff-container { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 20px; + .lexical-diff .field-diff-content { + .html-diff { + font-family: var(--font-serif); + font-size: base(0.8); + letter-spacing: 0.02em; } blockquote { font-size: base(0.8); margin-block: base(0.8); - margin-inline: base(0.2); - border-inline-start-color: var(--theme-elevation-150); - border-inline-start-width: base(0.2); - border-inline-start-style: solid; + margin-inline: 0; padding-inline-start: base(0.6); padding-block: base(0.2); + position: relative; // Required for absolute positioning of ::after - &:has([data-match-type='create']) { - border-inline-start-color: var(--theme-success-150); + &::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + inset-inline-start: 0; + width: base(0.2); + background-color: var(--theme-elevation-150); } - &:has([data-match-type='delete']) { - border-inline-start-color: var(--theme-error-150); + &:has([data-match-type='create'])::after { + background-color: var(--theme-success-150); + } + + &:has([data-match-type='delete'])::after { + background-color: var(--theme-error-150); } } @@ -35,45 +42,45 @@ h1 { padding: base(0.7) 0px base(0.55); - line-height: base(1.2); + line-height: base(1.5); font-weight: 600; font-size: base(1.4); font-family: var(--font-body); } h2 { padding: base(0.7) 0px base(0.5); - line-height: base(1); + line-height: base(1.4); font-weight: 600; font-size: base(1.25); font-family: var(--font-body); } h3 { - padding: base(0.65) 0px base(0.45); - line-height: base(0.9); + padding: base(0.6) 0px base(0.45); + line-height: base(1.4); font-weight: 600; font-size: base(1.1); font-family: var(--font-body); } h4 { - padding: base(0.65) 0px base(0.4); - line-height: base(0.7); + padding: base(0.4) 0px base(0.35); + line-height: base(1.5); font-weight: 600; - font-size: base(1); + font-size: base(1.05); font-family: var(--font-body); } h5 { - padding: base(0.65) 0px base(0.35); - line-height: base(0.5); + padding: base(0.3) 0px base(0.3); + line-height: base(1.4); font-weight: 600; font-size: base(0.9); font-family: var(--font-body); } h6 { - padding: base(0.65) 0px base(0.35); + padding: base(0.55) 0px base(0.25); line-height: base(0.5); font-weight: 600; - font-size: base(0.8); + font-size: base(0.75); font-family: var(--font-body); } diff --git a/packages/richtext-lexical/src/field/Diff/index.tsx b/packages/richtext-lexical/src/field/Diff/index.tsx index f670d8e9e..e12c1467f 100644 --- a/packages/richtext-lexical/src/field/Diff/index.tsx +++ b/packages/richtext-lexical/src/field/Diff/index.tsx @@ -1,14 +1,13 @@ import type { SerializedEditorState } from 'lexical' import type { RichTextFieldDiffServerComponent } from 'payload' -import { getTranslation } from '@payloadcms/translations' -import { FieldDiffLabel } from '@payloadcms/ui/rsc' -import React from 'react' +import { FieldDiffContainer, getHTMLDiffComponents } from '@payloadcms/ui/rsc' -import './htmlDiff/index.scss' import './index.scss' import '../bundled.css' +import React from 'react' + import type { HTMLConvertersFunctionAsync } from '../../features/converters/lexicalToHtml/async/types.js' import { convertLexicalToHTMLAsync } from '../../features/converters/lexicalToHtml/async/index.js' @@ -18,11 +17,18 @@ import { ListItemDiffHTMLConverterAsync } from './converters/listitem/index.js' import { RelationshipDiffHTMLConverterAsync } from './converters/relationship/index.js' import { UnknownDiffHTMLConverterAsync } from './converters/unknown/index.js' import { UploadDiffHTMLConverterAsync } from './converters/upload/index.js' -import { HtmlDiff } from './htmlDiff/index.js' + const baseClass = 'lexical-diff' export const LexicalDiffComponent: RichTextFieldDiffServerComponent = async (args) => { - const { comparisonValue, field, i18n, locale, versionValue } = args + const { + comparisonValue: valueFrom, + field, + i18n, + locale, + nestingLevel, + versionValue: valueTo, + } = args const converters: HTMLConvertersFunctionAsync = ({ defaultConverters }) => ({ ...defaultConverters, @@ -38,38 +44,37 @@ export const LexicalDiffComponent: RichTextFieldDiffServerComponent = async (arg depth: 1, req: args.req, }) - const comparisonHTML = await convertLexicalToHTMLAsync({ + const fromHTML = await convertLexicalToHTMLAsync({ converters, - data: comparisonValue as SerializedEditorState, + data: valueFrom as SerializedEditorState, + disableContainer: true, populate: payloadPopulateFn, }) - const versionHTML = await convertLexicalToHTMLAsync({ + const toHTML = await convertLexicalToHTMLAsync({ converters, - data: versionValue as SerializedEditorState, + data: valueTo as SerializedEditorState, + disableContainer: true, populate: payloadPopulateFn, }) - const diffHTML = new HtmlDiff(comparisonHTML, versionHTML) - - const [oldHTML, newHTML] = diffHTML.getSideBySideContents() + const { From, To } = getHTMLDiffComponents({ + // Ensure empty paragraph is displayed for empty rich text fields - otherwise, toHTML may be displayed in the wrong column + fromHTML: fromHTML?.length ? fromHTML : '

', + toHTML: toHTML?.length ? toHTML : '

', + }) return ( -
- - {locale && {locale}} - {'label' in field && - typeof field.label !== 'function' && - getTranslation(field.label || '', i18n)} - -
- {oldHTML && ( -
- )} - {newHTML && ( -
- )} -
-
+ ) } diff --git a/packages/translations/src/clientKeys.ts b/packages/translations/src/clientKeys.ts index 2d178606b..97f61a15e 100644 --- a/packages/translations/src/clientKeys.ts +++ b/packages/translations/src/clientKeys.ts @@ -236,6 +236,7 @@ export const clientTranslationKeys = createClientTranslationKeys([ 'general:livePreview', 'general:loading', 'general:locale', + 'general:locales', 'general:menu', 'general:moreOptions', 'general:move', @@ -409,15 +410,20 @@ export const clientTranslationKeys = createClientTranslationKeys([ 'version:autosave', 'version:autosavedSuccessfully', 'version:autosavedVersion', + 'version:versionAgo', + 'version:moreVersions', 'version:changed', 'version:changedFieldsCount', 'version:confirmRevertToSaved', - 'version:compareVersion', + 'version:compareVersions', + 'version:comparingAgainst', + 'version:currentlyViewing', 'version:confirmPublish', 'version:confirmUnpublish', 'version:confirmVersionRestoration', 'version:currentDraft', 'version:currentPublishedVersion', + 'version:currentlyPublished', 'version:draft', 'version:draftSavedSuccessfully', 'version:lastSavedAgo', @@ -427,6 +433,7 @@ export const clientTranslationKeys = createClientTranslationKeys([ 'version:noRowsSelected', 'version:preview', 'version:previouslyPublished', + 'version:previousVersion', 'version:problemRestoringVersion', 'version:publish', 'version:publishAllLocales', @@ -446,6 +453,7 @@ export const clientTranslationKeys = createClientTranslationKeys([ 'version:selectLocales', 'version:selectVersionToCompare', 'version:showLocales', + 'version:specificVersion', 'version:status', 'version:type', 'version:unpublish', diff --git a/packages/translations/src/importDateFNSLocale.ts b/packages/translations/src/importDateFNSLocale.ts index 24548891c..46af2225c 100644 --- a/packages/translations/src/importDateFNSLocale.ts +++ b/packages/translations/src/importDateFNSLocale.ts @@ -18,11 +18,12 @@ export const importDateFNSLocale = async (locale: string): Promise => { break case 'bn-BD': result = (await import('date-fns/locale/bn')).bn + break case 'bn-IN': result = (await import('date-fns/locale/bn')).bn - break + break case 'ca': result = (await import('date-fns/locale/ca')).ca diff --git a/packages/translations/src/languages/ar.ts b/packages/translations/src/languages/ar.ts index d8bea4c0e..c01604a6f 100644 --- a/packages/translations/src/languages/ar.ts +++ b/packages/translations/src/languages/ar.ts @@ -489,22 +489,28 @@ export const arTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} قام بتغيير الحقل', changedFieldsCount_other: '{{count}} حقول تم تغييرها', compareVersion: 'مقارنة النّسخة مع:', + compareVersions: 'قارن الإصدارات', + comparingAgainst: 'مقارنة مع', confirmPublish: 'تأكيد النّشر', confirmRevertToSaved: 'تأكيد الرّجوع للنسخة المنشورة', confirmUnpublish: 'تأكيد إلغاء النّشر', confirmVersionRestoration: 'تأكيد إستعادة النّسخة', currentDocumentStatus: 'المستند {{docStatus}} الحالي', currentDraft: 'المسودة الحالية', + currentlyPublished: 'نشر حاليا', + currentlyViewing: 'تمت المشاهدة حاليا', currentPublishedVersion: 'النسخة المنشورة الحالية', draft: 'مسودّة', draftSavedSuccessfully: 'تمّ حفظ المسودّة بنجاح.', lastSavedAgo: 'تم الحفظ آخر مرة قبل {{distance}}', modifiedOnly: 'تم التعديل فقط', + moreVersions: 'المزيد من الإصدارات...', noFurtherVersionsFound: 'لم يتمّ العثور على نسخات أخرى', noRowsFound: 'لم يتمّ العثور على {{label}}', noRowsSelected: 'لم يتم اختيار {{label}}', preview: 'معاينة', previouslyPublished: 'نشر سابقا', + previousVersion: 'النسخة السابقة', problemRestoringVersion: 'حدث خطأ في استعادة هذه النّسخة', publish: 'نشر', publishAllLocales: 'نشر جميع المواقع', @@ -525,10 +531,12 @@ export const arTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'حدّد نسخة للمقارنة', showingVersionsFor: 'يتمّ عرض النًّسخ ل:', showLocales: 'اظهر اللّغات:', + specificVersion: 'الإصدار المحدد', status: 'الحالة', unpublish: 'الغاء النّشر', unpublishing: 'يتمّ الغاء النّشر...', version: 'النّسخة', + versionAgo: 'منذ {{distance}}', versionCount_many: 'تمّ العثور على {{count}} نُسخ', versionCount_none: 'لم يتمّ العثور على أيّ من النّسخ', versionCount_one: 'تمّ العثور على {{count}} من النّسخ', diff --git a/packages/translations/src/languages/az.ts b/packages/translations/src/languages/az.ts index 1057b06c7..1d718f684 100644 --- a/packages/translations/src/languages/az.ts +++ b/packages/translations/src/languages/az.ts @@ -500,22 +500,28 @@ export const azTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} sahə dəyişdi', changedFieldsCount_other: '{{count}} dəyişdirilmiş sahələr', compareVersion: 'Versiyanı müqayisə et:', + compareVersions: 'Versiyaları Müqayisə Edin', + comparingAgainst: 'Müqayisə etmək', confirmPublish: 'Dərci təsdiq edin', confirmRevertToSaved: 'Yadda saxlanana qayıtmağı təsdiq edin', confirmUnpublish: 'Dərcdən çıxartmağı təsdiq edin', confirmVersionRestoration: 'Versiyanın bərpasını təsdiq edin', currentDocumentStatus: 'Cari {{docStatus}} sənədi', currentDraft: 'Hazırki Layihə', + currentlyPublished: 'Hazırda Nəşr Olunmuş', + currentlyViewing: 'Hazırda baxılır', currentPublishedVersion: 'Hazırki Nəşr Versiyası', draft: 'Qaralama', draftSavedSuccessfully: 'Qaralama uğurla yadda saxlandı.', lastSavedAgo: '{{distance}} əvvəl son yadda saxlanıldı', modifiedOnly: 'Yalnızca dəyişdirilmişdir', + moreVersions: 'Daha çox versiyalar...', noFurtherVersionsFound: 'Başqa versiyalar tapılmadı', noRowsFound: 'Heç bir {{label}} tapılmadı', noRowsSelected: 'Heç bir {{label}} seçilməyib', preview: 'Öncədən baxış', previouslyPublished: 'Daha əvvəl nəşr olunmuş', + previousVersion: 'Əvvəlki Versiya', problemRestoringVersion: 'Bu versiyanın bərpasında problem yaşandı', publish: 'Dərc et', publishAllLocales: 'Bütün lokalizasiyaları dərc edin', @@ -536,10 +542,12 @@ export const azTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Müqayisə üçün bir versiya seçin', showingVersionsFor: 'Göstərilən versiyalar üçün:', showLocales: 'Lokalları göstər:', + specificVersion: 'Xüsusi Versiya', status: 'Status', unpublish: 'Dərcdən çıxart', unpublishing: 'Dərcdən çıxarılır...', version: 'Versiya', + versionAgo: '{{distance}} əvvəl', versionCount_many: '{{count}} versiya tapıldı', versionCount_none: 'Versiya tapılmadı', versionCount_one: '{{count}} versiya tapıldı', diff --git a/packages/translations/src/languages/bg.ts b/packages/translations/src/languages/bg.ts index 4c44a87ce..bc3b7d04e 100644 --- a/packages/translations/src/languages/bg.ts +++ b/packages/translations/src/languages/bg.ts @@ -499,22 +499,28 @@ export const bgTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} променено поле', changedFieldsCount_other: '{{count}} променени полета', compareVersion: 'Сравни версия с:', + compareVersions: 'Сравняване на версии', + comparingAgainst: 'Сравнение с', confirmPublish: 'Потвърди публикуване', confirmRevertToSaved: 'Потвърди възстановяване до запазен', confirmUnpublish: 'Потвърди скриване', confirmVersionRestoration: 'Потвърди възстановяване на версия', currentDocumentStatus: 'Сегашен статус на документа: {{docStatus}}', currentDraft: 'Текущ проект', + currentlyPublished: 'В момента публикуван', + currentlyViewing: 'В момента преглеждате', currentPublishedVersion: 'Текуща публикувана версия', draft: 'Чернова', draftSavedSuccessfully: 'Чернова запазена успешно.', lastSavedAgo: 'последно запазено преди {{distance}}', modifiedOnly: 'Само променени', + moreVersions: 'Още версии...', noFurtherVersionsFound: 'Не са открити повече версии', noRowsFound: 'Не е открит {{label}}', noRowsSelected: 'Не е избран {{label}}', preview: 'Предварителен преглед', previouslyPublished: 'Предишно публикувано', + previousVersion: 'Предишна версия', problemRestoringVersion: 'Имаше проблем при възстановяването на тази версия', publish: 'Публикувай', publishAllLocales: 'Публикувайте всички локали', @@ -535,10 +541,12 @@ export const bgTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Избери версия за сравняване', showingVersionsFor: 'Показване на версии за:', showLocales: 'Покажи преводи:', + specificVersion: 'Специфична версия', status: 'Статус', unpublish: 'Скрий', unpublishing: 'Скриване...', version: 'Версия', + versionAgo: 'преди {{distance}}', versionCount_many: '{{count}} открити версии', versionCount_none: 'Няма открити версии', versionCount_one: '{{count}} открита версия', diff --git a/packages/translations/src/languages/bnBd.ts b/packages/translations/src/languages/bnBd.ts index 29391de3f..f4512b27f 100644 --- a/packages/translations/src/languages/bnBd.ts +++ b/packages/translations/src/languages/bnBd.ts @@ -500,22 +500,28 @@ export const bnBdTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} পরিবর্তিত ক্ষেত্র', changedFieldsCount_other: '{{count}} পরিবর্তিত ক্ষেত্রগুলি', compareVersion: 'সংস্করণের সাথে তুলনা করুন:', + compareVersions: 'সংস্করণ তুলনা করুন', + comparingAgainst: 'তুলনা করা যাচ্ছে', confirmPublish: 'প্রকাশ নিশ্চিত করুন', confirmRevertToSaved: 'সংরক্ষিত অবস্থায় ফিরে যাওয়া নিশ্চিত করুন', confirmUnpublish: 'আনপাবলিশ নিশ্চিত করুন', confirmVersionRestoration: 'সংস্করণ পুনরুদ্ধার নিশ্চিত করুন', currentDocumentStatus: 'বর্তমান {{docStatus}} ডকুমেন্ট', currentDraft: 'বর্তমান খসড়া', + currentlyPublished: 'বর্তমানে প্রকাশিত', + currentlyViewing: 'বর্তমানে দেখছেন', currentPublishedVersion: 'বর্তমান প্রকাশিত সংস্করণ', draft: 'খসড়া', draftSavedSuccessfully: 'খসড়া সফলভাবে সংরক্ষিত হয়েছে।', lastSavedAgo: 'সর্বশেষ সংরক্ষণ করা হয়েছে {{distance}} আগে', modifiedOnly: 'শুধুমাত্র পরিবর্তিত', + moreVersions: 'আরও সংস্করণ...', noFurtherVersionsFound: 'আর কোনো সংস্করণ পাওয়া যায়নি', noRowsFound: 'কোনো {{label}} পাওয়া যায়নি', noRowsSelected: 'কোনো {{label}} নির্বাচিত হয়নি', preview: 'প্রাকদর্শন', previouslyPublished: 'পূর্বে প্রকাশিত', + previousVersion: 'পূর্ববর্তী সংস্করণ', problemRestoringVersion: 'এই সংস্করণ পুনরুদ্ধারে সমস্যা হয়েছে', publish: 'প্রকাশ করুন', publishAllLocales: 'সমস্ত লোকেল প্রকাশ করুন', @@ -536,10 +542,12 @@ export const bnBdTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'তুলনার জন্য একটি সংস্করণ নির্বাচন করুন', showingVersionsFor: 'এর জন্য সংস্করণগুলি দেখানো হচ্ছে:', showLocales: 'লোকেল দেখান:', + specificVersion: 'নির্দিষ্ট সংস্করণ', status: 'স্থিতি', unpublish: 'প্রকাশ বাতিল করুন', unpublishing: 'প্রকাশ বাতিল করা হচ্ছে...', version: 'সংস্করণ', + versionAgo: '{{distance}} পূর্বে', versionCount_many: '{{count}}টি সংস্করণ পাওয়া গেছে', versionCount_none: 'কোনো সংস্করণ পাওয়া যায়নি', versionCount_one: '{{count}}টি সংস্করণ পাওয়া গেছে', diff --git a/packages/translations/src/languages/bnIn.ts b/packages/translations/src/languages/bnIn.ts index 2e128503a..c85406a11 100644 --- a/packages/translations/src/languages/bnIn.ts +++ b/packages/translations/src/languages/bnIn.ts @@ -500,22 +500,28 @@ export const bnInTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} পরিবর্তিত ক্ষেত্র', changedFieldsCount_other: '{{count}} পরিবর্তিত ক্ষেত্রগুলি', compareVersion: 'সংস্করণের সাথে তুলনা করুন:', + compareVersions: 'সংস্করণগুলি তুলনা করুন', + comparingAgainst: 'তুলনা করে', confirmPublish: 'প্রকাশ নিশ্চিত করুন', confirmRevertToSaved: 'সংরক্ষিত অবস্থায় ফিরে যাওয়া নিশ্চিত করুন', confirmUnpublish: 'আনপাবলিশ নিশ্চিত করুন', confirmVersionRestoration: 'সংস্করণ পুনরুদ্ধার নিশ্চিত করুন', currentDocumentStatus: 'বর্তমান {{docStatus}} ডকুমেন্ট', currentDraft: 'বর্তমান খসড়া', + currentlyPublished: 'বর্তমানে প্রকাশিত', + currentlyViewing: 'বর্তমানে দেখছেন', currentPublishedVersion: 'বর্তমান প্রকাশিত সংস্করণ', draft: 'খসড়া', draftSavedSuccessfully: 'খসড়া সফলভাবে সংরক্ষিত হয়েছে।', lastSavedAgo: 'সর্বশেষ সংরক্ষণ করা হয়েছে {{distance}} আগে', modifiedOnly: 'শুধুমাত্র পরিবর্তিত', + moreVersions: 'আরও সংস্করণ...', noFurtherVersionsFound: 'আর কোনো সংস্করণ পাওয়া যায়নি', noRowsFound: 'কোনো {{label}} পাওয়া যায়নি', noRowsSelected: 'কোনো {{label}} নির্বাচিত হয়নি', preview: 'প্রাকদর্শন', previouslyPublished: 'পূর্বে প্রকাশিত', + previousVersion: 'পূর্ববর্তী সংস্করণ', problemRestoringVersion: 'এই সংস্করণ পুনরুদ্ধারে সমস্যা হয়েছে', publish: 'প্রকাশ করুন', publishAllLocales: 'সমস্ত লোকেল প্রকাশ করুন', @@ -536,10 +542,12 @@ export const bnInTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'তুলনার জন্য একটি সংস্করণ নির্বাচন করুন', showingVersionsFor: 'এর জন্য সংস্করণগুলি দেখানো হচ্ছে:', showLocales: 'লোকেল দেখান:', + specificVersion: 'নির্দিষ্ট সংস্করণ', status: 'স্থিতি', unpublish: 'প্রকাশ বাতিল করুন', unpublishing: 'প্রকাশ বাতিল করা হচ্ছে...', version: 'সংস্করণ', + versionAgo: '{{distance}} পূর্বে', versionCount_many: '{{count}}টি সংস্করণ পাওয়া গেছে', versionCount_none: 'কোনো সংস্করণ পাওয়া যায়নি', versionCount_one: '{{count}}টি সংস্করণ পাওয়া গেছে', diff --git a/packages/translations/src/languages/ca.ts b/packages/translations/src/languages/ca.ts index 75eef6ebf..7c39fd487 100644 --- a/packages/translations/src/languages/ca.ts +++ b/packages/translations/src/languages/ca.ts @@ -502,22 +502,28 @@ export const caTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} camp canviat', changedFieldsCount_other: '{{count}} camps modificats', compareVersion: 'Comparar versió amb:', + compareVersions: 'Compara Versions', + comparingAgainst: 'Comparant amb', confirmPublish: 'Confirmar publicació', confirmRevertToSaved: 'Confirmar revertir a desat', confirmUnpublish: 'Confirmar despublicació', confirmVersionRestoration: 'Confirmar restauració de versió', currentDocumentStatus: 'Estat actual del document {{docStatus}}', currentDraft: 'Borrador actual', + currentlyPublished: 'Actualment publicat', + currentlyViewing: 'Actualment veient', currentPublishedVersion: 'Versió publicada actual', draft: 'Borrador', draftSavedSuccessfully: 'Borrador desat amb èxit.', lastSavedAgo: 'Últim desament fa {{distance}}', modifiedOnly: 'Només modificat', + moreVersions: 'Més versions...', noFurtherVersionsFound: "No s'han trobat més versions", noRowsFound: "No s'han trobat {{label}}", noRowsSelected: "No s'han seleccionat {{label}}", preview: 'Vista prèvia', previouslyPublished: 'Publicat anteriorment', + previousVersion: 'Versió anterior', problemRestoringVersion: 'Hi ha hagut un problema en restaurar aquesta versió', publish: 'Publicar', publishAllLocales: 'Publica totes les configuracions regionals', @@ -538,10 +544,12 @@ export const caTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Selecciona una versió per comparar', showingVersionsFor: 'Mostrant versions per a:', showLocales: 'Mostrar idiomes:', + specificVersion: 'Versió Específica', status: 'Estat', unpublish: 'Despublicar', unpublishing: 'Despublicant...', version: 'Versió', + versionAgo: 'fa {{distance}}', versionCount_many: '{{count}} versions trobades', versionCount_none: "No s'han trobat versions", versionCount_one: '{{count}} versió trobada', diff --git a/packages/translations/src/languages/cs.ts b/packages/translations/src/languages/cs.ts index 69187c4f4..7c3eee55c 100644 --- a/packages/translations/src/languages/cs.ts +++ b/packages/translations/src/languages/cs.ts @@ -496,22 +496,28 @@ export const csTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} změněné pole', changedFieldsCount_other: '{{count}} změněná pole', compareVersion: 'Porovnat verzi s:', + compareVersions: 'Porovnat verze', + comparingAgainst: 'Porovnání s', confirmPublish: 'Potvrďte publikování', confirmRevertToSaved: 'Potvrdit vrácení k uloženému', confirmUnpublish: 'Potvrdit zrušení publikování', confirmVersionRestoration: 'Potvrdit obnovení verze', currentDocumentStatus: 'Současný {{docStatus}} dokument', currentDraft: 'Současný koncept', + currentlyPublished: 'Aktuálně publikováno', + currentlyViewing: 'Aktuálně prohlížíte', currentPublishedVersion: 'Aktuálně publikovaná verze', draft: 'Koncept', draftSavedSuccessfully: 'Koncept úspěšně uložen.', lastSavedAgo: 'Naposledy uloženo před {{distance}}', modifiedOnly: 'Pouze upraveno', + moreVersions: 'Více verzí...', noFurtherVersionsFound: 'Nenalezeny další verze', noRowsFound: 'Nenalezen {{label}}', noRowsSelected: 'Nebyl vybrán žádný {{label}}', preview: 'Náhled', previouslyPublished: 'Dříve publikováno', + previousVersion: 'Předchozí verze', problemRestoringVersion: 'Při obnovování této verze došlo k problému', publish: 'Publikovat', publishAllLocales: 'Publikujte všechny lokalizace', @@ -532,10 +538,12 @@ export const csTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Vyberte verzi pro porovnání', showingVersionsFor: 'Zobrazují se verze pro:', showLocales: 'Zobrazit místní verze:', + specificVersion: 'Specifická verze', status: 'Stav', unpublish: 'Zrušit publikování', unpublishing: 'Zrušuji publikování...', version: 'Verze', + versionAgo: 'před {{distance}}', versionCount_many: '{{count}} verzí nalezeno', versionCount_none: 'Žádné verze nenalezeny', versionCount_one: '{{count}} verze nalezena', diff --git a/packages/translations/src/languages/da.ts b/packages/translations/src/languages/da.ts index f164bb77a..0c6cd3127 100644 --- a/packages/translations/src/languages/da.ts +++ b/packages/translations/src/languages/da.ts @@ -497,22 +497,28 @@ export const daTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} ændret felt', changedFieldsCount_other: '{{count}} ændrede felter', compareVersion: 'Sammenlign version med:', + compareVersions: 'Sammenlign versioner', + comparingAgainst: 'Sammenligner med', confirmPublish: 'Bekræft offentliggørelse', confirmRevertToSaved: 'Bekræft tilbagerulning til gemt', confirmUnpublish: 'Bekræft afpublicering', confirmVersionRestoration: 'Bekræft versionens gendannelse', currentDocumentStatus: 'Nuværende {{docStatus}} dokument', currentDraft: 'Nuværende kladde', + currentlyPublished: 'Aktuelt Offentliggjort', + currentlyViewing: 'Aktuelt visning', currentPublishedVersion: 'Nuværende offentliggjort version', draft: 'Kladde', draftSavedSuccessfully: 'Kladde gemt.', lastSavedAgo: 'Sidst gemt {{distance}}', modifiedOnly: 'Kun ændret', + moreVersions: 'Flere versioner...', noFurtherVersionsFound: 'Ingen yderligere versioner fundet', noRowsFound: 'Ingen {{label}} fundet', noRowsSelected: 'Ingen {{label}} valgt', preview: 'Forhåndsvisning', previouslyPublished: 'Tidligere offentliggjort', + previousVersion: 'Tidligere version', problemRestoringVersion: 'Der opstod et problem med at gendanne denne version', publish: 'Offentliggør', publishAllLocales: 'Udgiv alle lokalindstillinger', @@ -533,10 +539,12 @@ export const daTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Vælg en version til sammenligning', showingVersionsFor: 'Viser versioner for:', showLocales: 'Vis lokaliteter:', + specificVersion: 'Specifik Version', status: 'Status', unpublish: 'Afpublicer', unpublishing: 'Afpublicerer...', version: 'Version', + versionAgo: '{{distance}} siden', versionCount_many: '{{count}} versioner fundet', versionCount_none: 'Ingen versioner fundet', versionCount_one: '{{count}} version fundet', diff --git a/packages/translations/src/languages/de.ts b/packages/translations/src/languages/de.ts index 843ecd762..6cef09c93 100644 --- a/packages/translations/src/languages/de.ts +++ b/packages/translations/src/languages/de.ts @@ -506,22 +506,28 @@ export const deTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} geändertes Feld', changedFieldsCount_other: '{{count}} geänderte Felder', compareVersion: 'Vergleiche Version zu:', + compareVersions: 'Versionen vergleichen', + comparingAgainst: 'Im Vergleich zu', confirmPublish: 'Veröffentlichung bestätigen', confirmRevertToSaved: 'Zurücksetzen auf die letzte Speicherung bestätigen', confirmUnpublish: 'Aufhebung der Veröffentlichung bestätigen', confirmVersionRestoration: 'Wiederherstellung der Version bestätigen', currentDocumentStatus: 'Aktueller Dokumentenstatus: {{docStatus}}', currentDraft: 'Aktueller Entwurf', + currentlyPublished: 'Derzeit veröffentlicht', + currentlyViewing: 'Derzeitige Ansicht', currentPublishedVersion: 'Aktuell veröffentlichte Version', draft: 'Entwurf', draftSavedSuccessfully: 'Entwurf erfolgreich gespeichert.', lastSavedAgo: 'Zuletzt vor {{distance}} gespeichert', modifiedOnly: 'Nur modifiziert', + moreVersions: 'Mehr Versionen...', noFurtherVersionsFound: 'Keine weiteren Versionen vorhanden', noRowsFound: 'Kein {{label}} gefunden', noRowsSelected: 'Kein {{label}} ausgewählt', preview: 'Vorschau', previouslyPublished: 'Zuvor veröffentlicht', + previousVersion: 'Frühere Version', problemRestoringVersion: 'Bei der Wiederherstellung der Version ist ein Fehler aufgetreten', publish: 'Veröffentlichen', publishAllLocales: 'Alle Sprachen veröffentlichen', @@ -542,10 +548,12 @@ export const deTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Wähle Version zum Vergleich', showingVersionsFor: 'Versionen anzeigen für:', showLocales: 'Sprachen anzeigen:', + specificVersion: 'Spezifische Version', status: 'Status', unpublish: 'Veröffentlichung aufheben', unpublishing: 'Veröffentlichung aufheben...', version: 'Version', + versionAgo: 'vor {{distance}}', versionCount_many: '{{count}} Versionen gefunden', versionCount_none: 'Keine Versionen gefunden', versionCount_one: '{{count}} Version gefunden', diff --git a/packages/translations/src/languages/en.ts b/packages/translations/src/languages/en.ts index efcb025d0..bbcc55f1a 100644 --- a/packages/translations/src/languages/en.ts +++ b/packages/translations/src/languages/en.ts @@ -500,22 +500,28 @@ export const enTranslations = { changedFieldsCount_one: '{{count}} changed field', changedFieldsCount_other: '{{count}} changed fields', compareVersion: 'Compare version against:', + compareVersions: 'Compare Versions', + comparingAgainst: 'Comparing against', confirmPublish: 'Confirm publish', confirmRevertToSaved: 'Confirm revert to saved', confirmUnpublish: 'Confirm unpublish', - confirmVersionRestoration: 'Confirm version Restoration', + confirmVersionRestoration: 'Confirm Version Restoration', currentDocumentStatus: 'Current {{docStatus}} document', currentDraft: 'Current Draft', + currentlyPublished: 'Currently Published', + currentlyViewing: 'Currently viewing', currentPublishedVersion: 'Current Published Version', draft: 'Draft', draftSavedSuccessfully: 'Draft saved successfully.', lastSavedAgo: 'Last saved {{distance}} ago', modifiedOnly: 'Modified only', + moreVersions: 'More versions...', noFurtherVersionsFound: 'No further versions found', noRowsFound: 'No {{label}} found', noRowsSelected: 'No {{label}} selected', preview: 'Preview', previouslyPublished: 'Previously Published', + previousVersion: 'Previous Version', problemRestoringVersion: 'There was a problem restoring this version', publish: 'Publish', publishAllLocales: 'Publish all locales', @@ -536,10 +542,12 @@ export const enTranslations = { selectVersionToCompare: 'Select a version to compare', showingVersionsFor: 'Showing versions for:', showLocales: 'Show locales:', + specificVersion: 'Specific Version', status: 'Status', unpublish: 'Unpublish', unpublishing: 'Unpublishing...', version: 'Version', + versionAgo: '{{distance}} ago', versionCount_many: '{{count}} versions found', versionCount_none: 'No versions found', versionCount_one: '{{count}} version found', diff --git a/packages/translations/src/languages/es.ts b/packages/translations/src/languages/es.ts index 17369f47f..20a4bf10b 100644 --- a/packages/translations/src/languages/es.ts +++ b/packages/translations/src/languages/es.ts @@ -504,22 +504,28 @@ export const esTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} campo modificado', changedFieldsCount_other: '{{count}} campos modificados', compareVersion: 'Comparar versión con:', + compareVersions: 'Comparar Versiones', + comparingAgainst: 'Comparando contra', confirmPublish: 'Confirmar publicación', confirmRevertToSaved: 'Confirmar revertir a guardado', confirmUnpublish: 'Confirmar despublicación', confirmVersionRestoration: 'Confirmar restauración de versión', currentDocumentStatus: 'Documento actual: {{docStatus}}', currentDraft: 'Borrador actual', + currentlyPublished: 'Actualmente Publicado', + currentlyViewing: 'Actualmente viendo', currentPublishedVersion: 'Versión publicada actual', draft: 'Borrador', draftSavedSuccessfully: 'Borrador guardado con éxito.', lastSavedAgo: 'Guardado por última vez hace {{distance}}', modifiedOnly: 'Modificado solamente', + moreVersions: 'Más versiones...', noFurtherVersionsFound: 'No se encontraron más versiones', noRowsFound: 'No se encontraron {{label}}.', noRowsSelected: 'No se ha seleccionado ningún {{label}}.', preview: 'Vista previa', previouslyPublished: 'Publicado anteriormente', + previousVersion: 'Versión Anterior', problemRestoringVersion: 'Hubo un problema al restaurar esta versión', publish: 'Publicar', publishAllLocales: 'Publicar en todos los idiomas', @@ -540,10 +546,12 @@ export const esTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Seleccionar una versión para comparar', showingVersionsFor: 'Mostrando versiones para:', showLocales: 'Mostrar idiomas:', + specificVersion: 'Versión Específica', status: 'Estado', unpublish: 'Despublicar', unpublishing: 'Despublicando...', version: 'Versión', + versionAgo: 'hace {{distance}}', versionCount_many: '{{count}} versiones encontradas', versionCount_none: 'No se encontraron versiones', versionCount_one: '{{count}} versión encontrada', diff --git a/packages/translations/src/languages/et.ts b/packages/translations/src/languages/et.ts index 675ced36d..c750e5197 100644 --- a/packages/translations/src/languages/et.ts +++ b/packages/translations/src/languages/et.ts @@ -492,22 +492,28 @@ export const etTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} muudetud väli', changedFieldsCount_other: '{{count}} muudetud välja', compareVersion: 'Võrdle versiooni:', + compareVersions: 'Võrdle versioone', + comparingAgainst: 'Võrreldes vastu', confirmPublish: 'Kinnita avaldamine', confirmRevertToSaved: 'Kinnita taastamine salvestatud seisundisse', confirmUnpublish: 'Kinnita avaldamise tühistamine', confirmVersionRestoration: 'Kinnita versiooni taastamine', currentDocumentStatus: 'Praegune {{docStatus}} dokument', currentDraft: 'Praegune mustand', + currentlyPublished: 'Praegu avaldatud', + currentlyViewing: 'Praegu vaatamine', currentPublishedVersion: 'Praegune avaldatud versioon', draft: 'Mustand', draftSavedSuccessfully: 'Mustand edukalt salvestatud.', lastSavedAgo: 'Viimati salvestatud {{distance}} tagasi', modifiedOnly: 'Muudetud ainult', + moreVersions: 'Rohkem versioone...', noFurtherVersionsFound: 'Rohkem versioone ei leitud', noRowsFound: '{{label}} ei leitud', noRowsSelected: '{{label}} pole valitud', preview: 'Eelvaade', previouslyPublished: 'Varem avaldatud', + previousVersion: 'Eelmine versioon', problemRestoringVersion: 'Selle versiooni taastamisel tekkis probleem', publish: 'Avalda', publishAllLocales: 'Avaldage kõik lokaadid', @@ -528,10 +534,12 @@ export const etTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Vali versioon võrdlemiseks', showingVersionsFor: 'Näitan versioone:', showLocales: 'Näita keeli:', + specificVersion: 'Spetsiifiline versioon', status: 'Olek', unpublish: 'Tühista avaldamine', unpublishing: 'Avaldamise tühistamine...', version: 'Versioon', + versionAgo: '{{distance}} tagasi', versionCount_many: '{{count}} versiooni leitud', versionCount_none: 'Versioone ei leitud', versionCount_one: '{{count}} versioon leitud', diff --git a/packages/translations/src/languages/fa.ts b/packages/translations/src/languages/fa.ts index b296855e9..ff105c2f4 100644 --- a/packages/translations/src/languages/fa.ts +++ b/packages/translations/src/languages/fa.ts @@ -495,22 +495,28 @@ export const faTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} فیلد تغییر کرد', changedFieldsCount_other: '{{count}} فیلدهای تغییر یافته', compareVersion: 'مقایسه نگارش با:', + compareVersions: 'مقایسه نسخه ها', + comparingAgainst: 'مقایسه با', confirmPublish: 'تأیید انتشار', confirmRevertToSaved: 'تأیید بازگردانی نگارش ذخیره شده', confirmUnpublish: 'تأیید لغو انتشار', confirmVersionRestoration: 'تأیید بازیابی نگارش', currentDocumentStatus: 'جاری {{docStatus}} سند', currentDraft: 'پیش نویس فعلی', + currentlyPublished: 'منتشر شده است', + currentlyViewing: 'در حال مشاهده', currentPublishedVersion: 'نسخه منتشر شده فعلی', draft: 'پیش‌نویس', draftSavedSuccessfully: 'پیش‌نویس با موفقیت ذخیره شد.', lastSavedAgo: 'آخرین بار {{distance}} پیش ذخیره شد', modifiedOnly: 'تنها تغییر یافته', + moreVersions: 'نسخه های بیشتر...', noFurtherVersionsFound: 'نگارش دیگری یافت نشد', noRowsFound: 'هیچ {{label}} یافت نشد', noRowsSelected: 'هیچ {{label}} ای انتخاب نشده است', preview: 'پیش‌نمایش', previouslyPublished: 'قبلا منتشر شده', + previousVersion: 'نسخه قبلی', problemRestoringVersion: 'مشکلی در بازیابی این نگارش وجود دارد', publish: 'انتشار', publishAllLocales: 'انتشار در تمام مکان‌های محلی', @@ -531,10 +537,12 @@ export const faTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'نگارشی را برای مقایسه انتخاب کنید', showingVersionsFor: 'نمایش نگارش‌ها برای:', showLocales: 'نمایش زبان‌ها:', + specificVersion: 'نسخه مشخص', status: 'وضعیت', unpublish: 'لغو انتشار', unpublishing: 'در حال لغو انتشار...', version: 'نگارش', + versionAgo: '{{distance}} پیش', versionCount_many: '{{count}} نگارش‌ یافت شد', versionCount_none: 'هیچ نگارشی یافت نشد', versionCount_one: '{{count}} نگارش یافت شد', diff --git a/packages/translations/src/languages/fr.ts b/packages/translations/src/languages/fr.ts index bc32a24d3..b4116f3db 100644 --- a/packages/translations/src/languages/fr.ts +++ b/packages/translations/src/languages/fr.ts @@ -512,22 +512,28 @@ export const frTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} champ modifié', changedFieldsCount_other: '{{count}} champs modifiés', compareVersion: 'Comparez cette version à :', + compareVersions: 'Comparer les versions', + comparingAgainst: 'En comparaison avec', confirmPublish: 'Confirmer la publication', confirmRevertToSaved: 'Confirmer la restauration', confirmUnpublish: 'Confirmer l’annulation', confirmVersionRestoration: 'Confirmer la restauration de la version', currentDocumentStatus: 'Document {{docStatus}} actuel', currentDraft: 'Projet actuel', + currentlyPublished: 'Actuellement publié', + currentlyViewing: 'Actuellement en train de regarder', currentPublishedVersion: 'Version Publiée Actuelle', draft: 'Brouillon', draftSavedSuccessfully: 'Brouillon enregistré avec succès.', lastSavedAgo: 'Dernière sauvegarde il y a {{distance}}', modifiedOnly: 'Modifié uniquement', + moreVersions: 'Plus de versions...', noFurtherVersionsFound: 'Aucune autre version trouvée', noRowsFound: 'Aucun(e) {{label}} trouvé(e)', noRowsSelected: 'Aucune {{étiquette}} sélectionnée', preview: 'Aperçu', previouslyPublished: 'Précédemment publié', + previousVersion: 'Version Précédente', problemRestoringVersion: 'Un problème est survenu lors de la restauration de cette version', publish: 'Publier', publishAllLocales: 'Publier toutes les localités', @@ -548,10 +554,12 @@ export const frTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Sélectionnez une version à comparer', showingVersionsFor: 'Affichage des versions pour :', showLocales: 'Afficher les paramètres régionaux :', + specificVersion: 'Version spécifique', status: 'Statut', unpublish: 'Annuler la publication', unpublishing: 'Annulation en cours...', version: 'Version', + versionAgo: 'il y a {{distance}}', versionCount_many: '{{count}} versions trouvées', versionCount_none: 'Aucune version trouvée', versionCount_one: '{{count}} version trouvée', diff --git a/packages/translations/src/languages/he.ts b/packages/translations/src/languages/he.ts index 94870999d..8ccd637c2 100644 --- a/packages/translations/src/languages/he.ts +++ b/packages/translations/src/languages/he.ts @@ -483,22 +483,28 @@ export const heTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} שינה שדה', changedFieldsCount_other: '{{count}} שדות ששונו', compareVersion: 'השווה לגרסה:', + compareVersions: 'השווה גרסאות', + comparingAgainst: 'השוואה לעומת', confirmPublish: 'אישור פרסום', confirmRevertToSaved: 'אישור שחזור לגרסה שנשמרה', confirmUnpublish: 'אישור ביטול פרסום', confirmVersionRestoration: 'אישור שחזור גרסה', currentDocumentStatus: 'מסמך {{docStatus}} נוכחי', currentDraft: 'טיוטה נוכחית', + currentlyPublished: 'פורסם כרגע', + currentlyViewing: 'מציג כרגע', currentPublishedVersion: 'הגרסה שפורסמה כעת', draft: 'טיוטה', draftSavedSuccessfully: 'טיוטה נשמרה בהצלחה.', lastSavedAgo: 'נשמר לאחרונה לפני {{distance}}', modifiedOnly: 'מותאם בלבד', + moreVersions: 'עוד גרסאות...', noFurtherVersionsFound: 'לא נמצאו עוד גרסאות', noRowsFound: 'לא נמצאו {{label}}', noRowsSelected: 'לא נבחר {{תווית}}', preview: 'תצוגה מקדימה', previouslyPublished: 'פורסם בעבר', + previousVersion: 'גרסה קודמת', problemRestoringVersion: 'הייתה בעיה בשחזור הגרסה הזו', publish: 'פרסם', publishAllLocales: 'פרסם את כל המיקומים', @@ -519,10 +525,12 @@ export const heTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'בחר גרסה להשוואה', showingVersionsFor: 'מציג גרסאות עבור:', showLocales: 'הצג שפות:', + specificVersion: 'גרסה מסוימת', status: 'סטטוס', unpublish: 'בטל פרסום', unpublishing: 'מבטל פרסום...', version: 'גרסה', + versionAgo: 'לפני {{distance}}', versionCount_many: '{{count}} גרסאות נמצאו', versionCount_none: 'לא נמצאו גרסאות', versionCount_one: 'נמצאה גרסה אחת', diff --git a/packages/translations/src/languages/hr.ts b/packages/translations/src/languages/hr.ts index 6d40fae2a..ad630cba5 100644 --- a/packages/translations/src/languages/hr.ts +++ b/packages/translations/src/languages/hr.ts @@ -496,22 +496,28 @@ export const hrTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} promijenjeno polje', changedFieldsCount_other: '{{count}} promijenjena polja', compareVersion: 'Usporedi verziju sa:', + compareVersions: 'Usporedi verzije', + comparingAgainst: 'U usporedbi s', confirmPublish: 'Potvrdi objavu', confirmRevertToSaved: 'Potvrdite vraćanje na spremljeno', confirmUnpublish: 'Potvrdite poništavanje objave', confirmVersionRestoration: 'Potvrdite vraćanje verzije', currentDocumentStatus: 'Trenutni {{docStatus}} dokumenta', currentDraft: 'Trenutni Nacrt', + currentlyPublished: 'Trenutno objavljeno', + currentlyViewing: 'Trenutno pregledavate', currentPublishedVersion: 'Trenutno Objavljena Verzija', draft: 'Nacrt', draftSavedSuccessfully: 'Nacrt uspješno spremljen.', lastSavedAgo: 'Zadnji put spremljeno prije {{distance}', modifiedOnly: 'Samo modificirano', + moreVersions: 'Više verzija...', noFurtherVersionsFound: 'Nisu pronađene daljnje verzije', noRowsFound: '{{label}} nije pronađeno', noRowsSelected: 'Nije odabrana {{oznaka}}', preview: 'Pregled', previouslyPublished: 'Prethodno objavljeno', + previousVersion: 'Prethodna verzija', problemRestoringVersion: 'Nastao je problem pri vraćanju ove verzije', publish: 'Objaviti', publishAllLocales: 'Objavi sve lokalne postavke', @@ -532,10 +538,12 @@ export const hrTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Odaberite verziju za usporedbu', showingVersionsFor: 'Pokazujem verzije za:', showLocales: 'Prikaži jezike:', + specificVersion: 'Specifična verzija', status: 'Status', unpublish: 'Poništi objavu', unpublishing: 'Poništavanje objave...', version: 'Verzija', + versionAgo: 'prije {{distance}}', versionCount_many: '{{count}} pronađenih verzija', versionCount_none: 'Nema pronađenih verzija', versionCount_one: '{{count}} pronađena verzija', diff --git a/packages/translations/src/languages/hu.ts b/packages/translations/src/languages/hu.ts index 857ec3e48..3857b4a01 100644 --- a/packages/translations/src/languages/hu.ts +++ b/packages/translations/src/languages/hu.ts @@ -503,22 +503,28 @@ export const huTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} megváltozott mező', changedFieldsCount_other: '{{count}} módosított mező', compareVersion: 'Hasonlítsa össze a verziót a következőkkel:', + compareVersions: 'Verziók összehasonlítása', + comparingAgainst: 'Összehasonlítva', confirmPublish: 'A közzététel megerősítése', confirmRevertToSaved: 'Erősítse meg a mentett verzióra való visszatérést', confirmUnpublish: 'A közzététel visszavonásának megerősítése', confirmVersionRestoration: 'Verzió-visszaállítás megerősítése', currentDocumentStatus: 'Jelenlegi {{docStatus}} dokumentum', currentDraft: 'Aktuális tervezet', + currentlyPublished: 'Jelenleg közzétéve', + currentlyViewing: 'Jelenlegi megtekintés', currentPublishedVersion: 'Jelenleg Közzétett Verzió', draft: 'Piszkozat', draftSavedSuccessfully: 'A piszkozat sikeresen mentve.', lastSavedAgo: 'Utoljára mentve {{distance}} órája', modifiedOnly: 'Módosítva csak', + moreVersions: 'További verziók...', noFurtherVersionsFound: 'További verziók nem találhatók', noRowsFound: 'Nem található {{label}}', noRowsSelected: 'Nincs {{címke}} kiválasztva', preview: 'Előnézet', previouslyPublished: 'Korábban Közzétéve', + previousVersion: 'Előző Verzió', problemRestoringVersion: 'Hiba történt a verzió visszaállításakor', publish: 'Közzététel', publishAllLocales: 'Közzétesz az összes helyszínen', @@ -539,10 +545,12 @@ export const huTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Válassza ki az összehasonlítani kívánt verziót', showingVersionsFor: 'Verziók megjelenítése a következőkhöz:', showLocales: 'Nyelvek megjelenítése:', + specificVersion: 'Specifikus verzió', status: 'Állapot', unpublish: 'Közzététel visszavonása', unpublishing: 'Közzététel visszavonása...', version: 'Verzió', + versionAgo: '{{distance}} ezelőtt', versionCount_many: '{{count}} verzió található', versionCount_none: 'Nem található verzió', versionCount_one: '{{count}} verzió található', diff --git a/packages/translations/src/languages/hy.ts b/packages/translations/src/languages/hy.ts index 7fc6c061d..69b2e2bb9 100644 --- a/packages/translations/src/languages/hy.ts +++ b/packages/translations/src/languages/hy.ts @@ -506,22 +506,28 @@ export const hyTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} փոփոխված դաշտ', changedFieldsCount_other: '{{count}} փոփոխված դաշտեր', compareVersion: 'Համեմատել տարբերակը հետևյալի հետ՝', + compareVersions: 'Համեմատել տարբերակները', + comparingAgainst: 'Համեմատել հետևյալի հետ', confirmPublish: 'Հաստատել հրապարակումը', confirmRevertToSaved: 'Հաստատել վերադարձը պահպանված վիճակին', confirmUnpublish: 'Հաստատել վերադարձը չհրապարակված վիճակին։', confirmVersionRestoration: 'Հաստատել տարբերակի վերականգնումը', currentDocumentStatus: 'Ընթացիկ {{docStatus}} փաստաթուղթ', currentDraft: 'Ընթացիկ սևագիր', + currentlyPublished: 'Ներկայումս հրատարակված', + currentlyViewing: 'Ներկայումս դիտում է', currentPublishedVersion: 'Ընթացիկ հրապարակված տարբերակ', draft: 'Սևագիր', draftSavedSuccessfully: 'Սևագիրը հաջողությամբ պահպանվել է։', lastSavedAgo: 'Վերջին անգամ պահպանվել է {{distance}} առաջ', modifiedOnly: 'Միայն փոփոխված', + moreVersions: 'Ավելի շատ տարբերակներ...', noFurtherVersionsFound: 'Այլ տարբերակներ չեն գտնվել', noRowsFound: ' Ոչ մի {{label}} չի գտնվել', noRowsSelected: 'Ոչ մի {{label}} ընտրված չէ', preview: 'Նախադիտում', previouslyPublished: 'Նախկինում հրապարակված', + previousVersion: 'Նախորդ Տարբերակ', problemRestoringVersion: 'Այս տարբերակը վերականգնելու ժամանակ խնդիր է առաջացել', publish: 'Հրապարակել', publishAllLocales: 'Հրապարակել բոլոր լոկալներում', @@ -542,10 +548,12 @@ export const hyTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Ընտրեք տարբերակ՝ համեմատելու համար', showingVersionsFor: 'Ցուցադրված են տարբերակները՝', showLocales: 'Ցուցադրել լոկալները՝', + specificVersion: 'Մասնավոր Տարբերակ', status: 'Կարգավիճակ', unpublish: 'Բերել չհրապարակված վիճակի։', unpublishing: 'Բերվում է չհրապարակված վիճակի...', version: 'Տարբերակ', + versionAgo: '{{distance}} առաջ', versionCount_many: 'Գտնվել են {{count}} տարբերակներ', versionCount_none: 'Ոչ մի տարբերակ չի գտնվել', versionCount_one: '{{count}} տարբերակ է գտնվել', diff --git a/packages/translations/src/languages/it.ts b/packages/translations/src/languages/it.ts index 42301a92f..cb361c51e 100644 --- a/packages/translations/src/languages/it.ts +++ b/packages/translations/src/languages/it.ts @@ -503,22 +503,28 @@ export const itTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} campo modificato', changedFieldsCount_other: '{{count}} campi modificati', compareVersion: 'Confronta versione con:', + compareVersions: 'Confronta Versioni', + comparingAgainst: 'Confrontando con', confirmPublish: 'Conferma la pubblicazione', confirmRevertToSaved: 'Conferma il ripristino dei salvataggi', confirmUnpublish: 'Conferma annullamento della pubblicazione', confirmVersionRestoration: 'Conferma il ripristino della versione', currentDocumentStatus: 'Documento {{docStatus}} corrente', currentDraft: 'Bozza Corrente', + currentlyPublished: 'Attualmente Pubblicato', + currentlyViewing: 'Visualizzazione attuale', currentPublishedVersion: 'Versione Pubblicata Attuale', draft: 'Bozza', draftSavedSuccessfully: 'Bozza salvata con successo.', lastSavedAgo: 'Ultimo salvataggio {{distance}} fa', modifiedOnly: 'Modificato solo', + moreVersions: 'Altre versioni...', noFurtherVersionsFound: 'Non sono state trovate ulteriori versioni', noRowsFound: 'Nessun {{label}} trovato', noRowsSelected: 'Nessuna {{etichetta}} selezionata', preview: 'Anteprima', previouslyPublished: 'Precedentemente Pubblicato', + previousVersion: 'Versione Precedente', problemRestoringVersion: 'Si è verificato un problema durante il ripristino di questa versione', publish: 'Pubblicare', publishAllLocales: 'Pubblica tutte le località', @@ -539,10 +545,12 @@ export const itTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Seleziona una versione da confrontare', showingVersionsFor: 'Mostra le versioni per:', showLocales: 'Mostra localizzazioni:', + specificVersion: 'Versione Specifica', status: 'Stato', unpublish: 'Annulla pubblicazione', unpublishing: 'Annullamento pubblicazione...', version: 'Versione', + versionAgo: '{{distance}} fa', versionCount_many: '{{count}} versioni trovate', versionCount_none: 'Nessuna versione trovata', versionCount_one: '{{count}} versione trovata', diff --git a/packages/translations/src/languages/ja.ts b/packages/translations/src/languages/ja.ts index 0f0c7c391..fd03cff3c 100644 --- a/packages/translations/src/languages/ja.ts +++ b/packages/translations/src/languages/ja.ts @@ -497,22 +497,28 @@ export const jaTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} 変更されたフィールド', changedFieldsCount_other: '{{count}}つの変更されたフィールド', compareVersion: 'バージョンを比較:', + compareVersions: 'バージョンを比較する', + comparingAgainst: '比較対象とする', confirmPublish: '公開を確認する', confirmRevertToSaved: '保存された状態に戻す確認', confirmUnpublish: '非公開の確認', confirmVersionRestoration: 'バージョン復元の確認', currentDocumentStatus: '現在の {{docStatus}} データ', currentDraft: '現行の草案', + currentlyPublished: '現在公開中', + currentlyViewing: '現在表示中', currentPublishedVersion: '現在公開されているバージョン', draft: 'ドラフト', draftSavedSuccessfully: '下書きは正常に保存されました。', lastSavedAgo: '{{distance}}前に最後に保存されました', modifiedOnly: '変更済みのみ', + moreVersions: 'さらに多くのバージョン...', noFurtherVersionsFound: 'その他のバージョンは見つかりませんでした。', noRowsFound: '{{label}} は未設定です', noRowsSelected: '選択された{{label}}はありません', preview: 'プレビュー', previouslyPublished: '以前に公開された', + previousVersion: '以前のバージョン', problemRestoringVersion: 'このバージョンの復元に問題がありました。', publish: '公開する', publishAllLocales: 'すべてのロケールを公開する', @@ -533,10 +539,12 @@ export const jaTranslations: DefaultTranslationsObject = { selectVersionToCompare: '比較するバージョンを選択', showingVersionsFor: '次のバージョンを表示します:', showLocales: 'ロケールを表示:', + specificVersion: '特定のバージョン', status: 'ステータス', unpublish: '非公開', unpublishing: '非公開中...', version: 'バージョン', + versionAgo: '{{distance}}前', versionCount_many: '{{count}} バージョンがあります', versionCount_none: 'バージョンがありません', versionCount_one: '{{count}} バージョンがあります', diff --git a/packages/translations/src/languages/ko.ts b/packages/translations/src/languages/ko.ts index 7c583e2db..a12aab2a1 100644 --- a/packages/translations/src/languages/ko.ts +++ b/packages/translations/src/languages/ko.ts @@ -491,22 +491,28 @@ export const koTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} 변경된 필드', changedFieldsCount_other: '{{count}}개의 변경된 필드', compareVersion: '비교할 버전 선택:', + compareVersions: '버전 비교', + comparingAgainst: '비교 대상으로', confirmPublish: '게시하기', confirmRevertToSaved: '저장된 상태로 되돌리기', confirmUnpublish: '게시 해제하기', confirmVersionRestoration: '버전 복원하기', currentDocumentStatus: '현재 {{docStatus}} 문서', currentDraft: '현재 초안', + currentlyPublished: '현재 게시됨', + currentlyViewing: '현재 보고 있습니다', currentPublishedVersion: '현재 게시된 버전', draft: '초안', draftSavedSuccessfully: '초안이 저장되었습니다.', lastSavedAgo: '마지막으로 저장한지 {{distance}} 전', modifiedOnly: '수정된 것만', + moreVersions: '더 많은 버전...', noFurtherVersionsFound: '더 이상의 버전을 찾을 수 없습니다.', noRowsFound: '{{label}}을(를) 찾을 수 없음', noRowsSelected: '선택된 {{label}} 없음', preview: '미리보기', previouslyPublished: '이전에 발표된', + previousVersion: '이전 버전', problemRestoringVersion: '이 버전을 복원하는 중 문제가 발생했습니다.', publish: '게시', publishAllLocales: '모든 로케일을 게시하십시오', @@ -527,10 +533,12 @@ export const koTranslations: DefaultTranslationsObject = { selectVersionToCompare: '비교할 버전 선택', showingVersionsFor: '다음 버전 표시 중:', showLocales: 'locale 표시:', + specificVersion: '특정 버전', status: '상태', unpublish: '게시 해제', unpublishing: '게시 해제 중...', version: '버전', + versionAgo: '{{distance}} 전', versionCount_many: '{{count}}개의 버전을 찾았습니다', versionCount_none: '버전을 찾을 수 없습니다', versionCount_one: '{{count}}개의 버전을 찾았습니다', diff --git a/packages/translations/src/languages/lt.ts b/packages/translations/src/languages/lt.ts index afd2035fa..cdea6d9e8 100644 --- a/packages/translations/src/languages/lt.ts +++ b/packages/translations/src/languages/lt.ts @@ -503,22 +503,28 @@ export const ltTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} pakeistas laukas', changedFieldsCount_other: '{{count}} pakeisti laukai', compareVersion: 'Palyginkite versiją su:', + compareVersions: 'Palyginkite versijas', + comparingAgainst: 'Lyginant su', confirmPublish: 'Patvirtinkite publikaciją', confirmRevertToSaved: 'Patvirtinkite grįžimą į įrašytą', confirmUnpublish: 'Patvirtinkite nepublikavimą', confirmVersionRestoration: 'Patvirtinkite versijos atkūrimą', currentDocumentStatus: 'Dabartinis {{docStatus}} dokumentas', currentDraft: 'Dabartinis projektas', + currentlyPublished: 'Šiuo metu publikuojama', + currentlyViewing: 'Šiuo metu peržiūrima', currentPublishedVersion: 'Dabartinė publikuota versija', draft: 'Projektas', draftSavedSuccessfully: 'Juosmuo sėkmingai išsaugotas.', lastSavedAgo: 'Paskutinį kartą išsaugota prieš {{distance}}', modifiedOnly: 'Tik modifikuotas', + moreVersions: 'Daugiau versijų...', noFurtherVersionsFound: 'Nerasta daugiau versijų', noRowsFound: 'Nerasta {{label}}', noRowsSelected: 'Pasirinkta ne viena {{label}}', preview: 'Peržiūra', previouslyPublished: 'Ankstesnė publikacija', + previousVersion: 'Ankstesnė versija', problemRestoringVersion: 'Buvo problema atkuriant šią versiją', publish: 'Paskelbti', publishAllLocales: 'Publikuokite visus lokalizacijas', @@ -539,10 +545,12 @@ export const ltTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Pasirinkite versiją, kurią norite palyginti', showingVersionsFor: 'Rodomos versijos:', showLocales: 'Rodyti lokalizacijas:', + specificVersion: 'Specifinė versija', status: 'Būsena', unpublish: 'Nebepublikuoti', unpublishing: 'Nebepublikuojama...', version: 'Versija', + versionAgo: 'prieš {{distance}}', versionCount_many: 'Rasta {{count}} versijų', versionCount_none: 'Nerasta jokių versijų', versionCount_one: 'Rasta {{count}} versija', diff --git a/packages/translations/src/languages/lv.ts b/packages/translations/src/languages/lv.ts index e27d257ab..770e11a34 100644 --- a/packages/translations/src/languages/lv.ts +++ b/packages/translations/src/languages/lv.ts @@ -498,22 +498,28 @@ export const lvTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} mainīts lauks', changedFieldsCount_other: '{{count}} mainīti lauki', compareVersion: 'Salīdzināt versiju ar:', + compareVersions: 'Salīdzināt versijas', + comparingAgainst: 'Salīdzinot ar', confirmPublish: 'Apstiprināt publicēšanu', confirmRevertToSaved: 'Apstiprināt atgriešanu uz saglabāto', confirmUnpublish: 'Apstiprināt publicēšanas atcelšanu', confirmVersionRestoration: 'Apstiprināt versijas atjaunošanu', currentDocumentStatus: 'Pašreizējais {{docStatus}} dokuments', currentDraft: 'Pašreizējais melnraksts', + currentlyPublished: 'Pašlaik publicēts', + currentlyViewing: 'Pašlaik skatās', currentPublishedVersion: 'Pašreizējā publicētā versija', draft: 'Melnraksts', draftSavedSuccessfully: 'Melnraksts veiksmīgi saglabāts.', lastSavedAgo: 'Pēdējo reizi saglabāts pirms {{distance}}', modifiedOnly: 'Tikai modificētie', + moreVersions: 'Vairāk versijas...', noFurtherVersionsFound: 'Papildu versijas nav atrastas', noRowsFound: 'Nav atrasts neviens {{label}}', noRowsSelected: 'Nav atlasīts neviens {{label}}', preview: 'Priekšskatījums', previouslyPublished: 'Iepriekš publicēts', + previousVersion: 'Iepriekšējā versija', problemRestoringVersion: 'Radās problēma, atjaunojot šo versiju', publish: 'Publicēt', publishAllLocales: 'Publicēt visas lokalizācijas', @@ -534,10 +540,12 @@ export const lvTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Izvēlēties versiju salīdzināšanai', showingVersionsFor: 'Rāda versijas priekš:', showLocales: 'Rādīt lokalizācijas:', + specificVersion: 'Konkrētā versija', status: 'Statuss', unpublish: 'Atcelt publicēšanu', unpublishing: 'Atceļ publicēšanu...', version: 'Versija', + versionAgo: '{{distance}} pirms', versionCount_many: 'Atrastas {{count}} versijas', versionCount_none: 'Nav atrastu versiju', versionCount_one: 'Atrasta {{count}} versija', diff --git a/packages/translations/src/languages/my.ts b/packages/translations/src/languages/my.ts index 7f1dd43cc..7a7ff3b42 100644 --- a/packages/translations/src/languages/my.ts +++ b/packages/translations/src/languages/my.ts @@ -507,22 +507,28 @@ export const myTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} field telah diubah', changedFieldsCount_other: '{{count}}ကယ်လက်ရှိအရာများပြောင်းလဲလိုက်သည်', compareVersion: 'ဗားရှင်းနှင့် နှိုင်းယှဉ်ချက်:', + compareVersions: 'Bandingkan Versi', + comparingAgainst: 'Bandingkan dengan', confirmPublish: 'ထုတ်ဝေအတည်ပြုပါ။', confirmRevertToSaved: 'သိမ်းဆည်းပြီးကြောင်း အတည်ပြုပါ။', confirmUnpublish: 'အများဆိုင်ကို ဖျက်ရန် အတည်ပြုပါ။', confirmVersionRestoration: 'ဗားရှင်းပြန်လည် အသုံးပြုခြင်းကို အတည်ပြုပါ။', currentDocumentStatus: 'လက်ရှိ {{docStatus}} ဖိုင်', currentDraft: 'Draf Semasa', + currentlyPublished: 'လက်ရှိထုတ်ဝေရေးသားနေ', + currentlyViewing: 'Sedang melihat sekarang', currentPublishedVersion: 'လက်ရှိထုတ်ဝေထားသောဗားရှင်း', draft: 'မူကြမ်း', draftSavedSuccessfully: 'မူကြမ်းကို အောင်မြင်စွာ သိမ်းဆည်းပြီးပါပြီ။', lastSavedAgo: 'နောက်ဆုံး သိမ်းချက် {{distance}} ကြာပြီး', modifiedOnly: 'Hanya diubah', + moreVersions: 'ပိုမိုများသော ဗားရှင်းများ...', noFurtherVersionsFound: 'နောက်ထပ်ဗားရှင်းများ မတွေ့ပါ။', noRowsFound: '{{label}} အားမတွေ့ပါ။', noRowsSelected: 'Tiada {{label}} yang dipilih', preview: 'နမူနာပြရန်', previouslyPublished: 'တိုင်းရင်းသားထုတ်ဝေခဲ့', + previousVersion: 'Versi Sebelumnya', problemRestoringVersion: 'ဤဗားရှင်းကို ပြန်လည်ရယူရာတွင် ပြဿနာရှိနေသည်။', publish: 'ထုတ်ဝေသည်။', publishAllLocales: 'နိုင်ငံတကာစာလုံးအားလုံးကို ထုတ်ဝေပါ', @@ -543,10 +549,12 @@ export const myTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'နှိုင်းယှဉ်ရန် ဗားရှင်းကို ရွေးပါ။', showingVersionsFor: 'အတွက် ဗားရှင်းများကို ပြသနေသည်-', showLocales: 'ဒေသန္တရများကိုပြပါ။:', + specificVersion: 'အထူးဗားရှင်း', status: 'အခြေအနေ', unpublish: 'ပြန်ဖြုတ်မည်။', unpublishing: 'ပြန်ဖြုတ်နေဆဲ ...', version: 'ဗားရှင်း', + versionAgo: '{{distance}} ကြာပြီ', versionCount_many: '{{count}} ဗားရှင်းများကို တွေ့ပါသည်။', versionCount_none: 'ဗားရှင်းရှာဖွေ့ပါ။', versionCount_one: '{{count}} ဗားရှင်အား တွေ့ပါသည်။', diff --git a/packages/translations/src/languages/nb.ts b/packages/translations/src/languages/nb.ts index d1e0182de..3c18cb819 100644 --- a/packages/translations/src/languages/nb.ts +++ b/packages/translations/src/languages/nb.ts @@ -500,22 +500,28 @@ export const nbTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} endret felt', changedFieldsCount_other: '{{count}} endrede felt', compareVersion: 'Sammenlign versjon mot:', + compareVersions: 'Sammenlign Versjoner', + comparingAgainst: 'Sammenligner mot', confirmPublish: 'Bekreft publisering', confirmRevertToSaved: 'Bekreft tilbakestilling til lagret', confirmUnpublish: 'Bekreft avpublisering', confirmVersionRestoration: 'Bekreft versjon-gjenoppretting', currentDocumentStatus: 'Nåværende {{docStatus}} dokument', currentDraft: 'Nåværende utkast', + currentlyPublished: 'For tiden publisert', + currentlyViewing: 'Ser på for øyeblikket', currentPublishedVersion: 'Nåværende Publiserte Versjon', draft: 'Utkast', draftSavedSuccessfully: 'Utkast lagret.', lastSavedAgo: 'Sist lagret {{distance}} siden', modifiedOnly: 'Endret kun', + moreVersions: 'Flere versjoner...', noFurtherVersionsFound: 'Ingen flere versjoner funnet', noRowsFound: 'Ingen {{label}} funnet', noRowsSelected: 'Ingen {{label}} valgt', preview: 'Forhåndsvisning', previouslyPublished: 'Tidligere Publisert', + previousVersion: 'Tidligere versjon', problemRestoringVersion: 'Det oppstod et problem med gjenoppretting av denne versjonen', publish: 'Publisere', publishAllLocales: 'Publiser alle språk', @@ -536,10 +542,12 @@ export const nbTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Velg en versjon å sammenligne', showingVersionsFor: 'Viser versjoner for:', showLocales: 'Vis språk:', + specificVersion: 'Spesifikk versjon', status: 'Status', unpublish: 'Avpubliser', unpublishing: 'Avpubliserer...', version: 'Versjon', + versionAgo: '{{distance}} siden', versionCount_many: '{{count}} versjoner funnet', versionCount_none: 'Ingen versjoner funnet', versionCount_one: '{{count}} versjon funnet', diff --git a/packages/translations/src/languages/nl.ts b/packages/translations/src/languages/nl.ts index 1a68e2910..1ac27ef71 100644 --- a/packages/translations/src/languages/nl.ts +++ b/packages/translations/src/languages/nl.ts @@ -504,22 +504,28 @@ export const nlTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} gewijzigd veld', changedFieldsCount_other: '{{count}} gewijzigde velden', compareVersion: 'Vergelijk versie met:', + compareVersions: 'Vergelijk Versies', + comparingAgainst: 'Vergeleken met', confirmPublish: 'Bevestig publiceren', confirmRevertToSaved: 'Bevestig terugdraaien naar bewaarde versie', confirmUnpublish: 'Bevestig depubliceren', confirmVersionRestoration: 'Bevestig te herstellen versie', currentDocumentStatus: 'Huidig {{docStatus}} document', currentDraft: 'Huidige Concept', + currentlyPublished: 'Momenteel gepubliceerd', + currentlyViewing: 'Momenteel bekijken', currentPublishedVersion: 'Huidige Gepubliceerde Versie', draft: 'Concept', draftSavedSuccessfully: 'Concept succesvol bewaard.', lastSavedAgo: 'Laatst opgeslagen {{distance}} geleden', modifiedOnly: 'Alleen gewijzigd', + moreVersions: 'Meer versies...', noFurtherVersionsFound: 'Geen verdere versies gevonden', noRowsFound: 'Geen {{label}} gevonden', noRowsSelected: 'Geen {{label}} geselecteerd', preview: 'Voorbeeld', previouslyPublished: 'Eerder gepubliceerd', + previousVersion: 'Vorige Versie', problemRestoringVersion: 'Er was een probleem bij het herstellen van deze versie', publish: 'Publiceren', publishAllLocales: 'Publiceer alle taalinstellingen', @@ -540,10 +546,12 @@ export const nlTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Selecteer een versie om te vergelijken', showingVersionsFor: 'Versies tonen voor:', showLocales: 'Toon talen:', + specificVersion: 'Specifieke versie', status: 'Status', unpublish: 'Publicatie ongedaan maken', unpublishing: 'Publicatie ongedaan maken...', version: 'Versie', + versionAgo: '{{distance}} geleden', versionCount_many: '{{count}} versies gevonden', versionCount_none: 'Geen versies gevonden', versionCount_one: '{{count}} versie gevonden', diff --git a/packages/translations/src/languages/pl.ts b/packages/translations/src/languages/pl.ts index 5eeec6b5e..71d742c0f 100644 --- a/packages/translations/src/languages/pl.ts +++ b/packages/translations/src/languages/pl.ts @@ -499,22 +499,28 @@ export const plTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} zmienione pole', changedFieldsCount_other: '{{count}} zmienione pola', compareVersion: 'Porównaj wersję z:', + compareVersions: 'Porównaj Wersje', + comparingAgainst: 'Porównując do', confirmPublish: 'Potwierdź publikację', confirmRevertToSaved: 'Potwierdź powrót do zapisanego', confirmUnpublish: 'Potwierdź cofnięcie publikacji', confirmVersionRestoration: 'Potwierdź przywrócenie wersji', currentDocumentStatus: 'Bieżący status {{docStatus}} dokumentu', currentDraft: 'Aktualna wersja robocza', + currentlyPublished: 'Obecnie opublikowane', + currentlyViewing: 'Obecnie przeglądasz', currentPublishedVersion: 'Aktualna Opublikowana Wersja', draft: 'Szkic', draftSavedSuccessfully: 'Wersja robocza została pomyślnie zapisana.', lastSavedAgo: 'Ostatnio zapisane {{distance}} temu', modifiedOnly: 'Tylko zmodyfikowany', + moreVersions: 'Więcej wersji...', noFurtherVersionsFound: 'Nie znaleziono dalszych wersji', noRowsFound: 'Nie znaleziono {{label}}', noRowsSelected: 'Nie wybrano {{etykieta}}', preview: 'Podgląd', previouslyPublished: 'Wcześniej opublikowane', + previousVersion: 'Poprzednia Wersja', problemRestoringVersion: 'Wystąpił problem podczas przywracania tej wersji', publish: 'Publikuj', publishAllLocales: 'Opublikuj wszystkie lokalizacje', @@ -535,10 +541,12 @@ export const plTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Wybierz wersję do porównania', showingVersionsFor: 'Wyświetlanie wersji dla:', showLocales: 'Pokaż ustawienia regionalne:', + specificVersion: 'Konkretna Wersja', status: 'Status', unpublish: 'Cofnij publikację', unpublishing: 'Cofanie publikacji...', version: 'Wersja', + versionAgo: '{{distance}} temu', versionCount_many: 'Znalezionych wersji: {{count}}', versionCount_none: 'Nie znaleziono wersji', versionCount_one: 'Znaleziono {{count}} wersję', diff --git a/packages/translations/src/languages/pt.ts b/packages/translations/src/languages/pt.ts index 691cb46b3..b1d0930d5 100644 --- a/packages/translations/src/languages/pt.ts +++ b/packages/translations/src/languages/pt.ts @@ -500,22 +500,28 @@ export const ptTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} campo alterado', changedFieldsCount_other: '{{count}} campos alterados', compareVersion: 'Comparar versão com:', + compareVersions: 'Comparar Versões', + comparingAgainst: 'Comparando com', confirmPublish: 'Confirmar publicação', confirmRevertToSaved: 'Confirmar a reversão para o salvo', confirmUnpublish: 'Confirmar despublicação', confirmVersionRestoration: 'Confirmar Restauração de versão', currentDocumentStatus: 'Documento {{docStatus}} atual', currentDraft: 'Rascunho Atual', + currentlyPublished: 'Atualmente Publicado', + currentlyViewing: 'Atualmente visualizando', currentPublishedVersion: 'Versão Publicada Atual', draft: 'Rascunho', draftSavedSuccessfully: 'Rascunho salvo com sucesso.', lastSavedAgo: 'Última gravação há {{distance}}', modifiedOnly: 'Modificado apenas', + moreVersions: 'Mais versões...', noFurtherVersionsFound: 'Nenhuma outra versão encontrada', noRowsFound: 'Nenhum(a) {{label}} encontrado(a)', noRowsSelected: 'Nenhum {{rótulo}} selecionado', preview: 'Pré-visualização', previouslyPublished: 'Publicado Anteriormente', + previousVersion: 'Versão Anterior', problemRestoringVersion: 'Ocorreu um problema ao restaurar essa versão', publish: 'Publicar', publishAllLocales: 'Publicar todas as localidades', @@ -536,10 +542,12 @@ export const ptTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Selecione uma versão para comparar', showingVersionsFor: 'Mostrando versões para:', showLocales: 'Exibir localizações:', + specificVersion: 'Versão Específica', status: 'Status', unpublish: 'Despublicar', unpublishing: 'Despublicando...', version: 'Versão', + versionAgo: 'há {{distance}}', versionCount_many: '{{count}} versões encontradas', versionCount_none: 'Nenhuma versão encontrada', versionCount_one: '{{count}} versão encontrada', diff --git a/packages/translations/src/languages/ro.ts b/packages/translations/src/languages/ro.ts index 0c23f35e5..ff06f3b47 100644 --- a/packages/translations/src/languages/ro.ts +++ b/packages/translations/src/languages/ro.ts @@ -507,22 +507,28 @@ export const roTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} a modificat câmpul', changedFieldsCount_other: '{{count}} câmpuri modificate', compareVersion: 'Comparați versiunea cu:', + compareVersions: 'Compară Versiuni', + comparingAgainst: 'Comparând cu', confirmPublish: 'Confirmați publicarea', confirmRevertToSaved: 'Confirmați revenirea la starea salvată', confirmUnpublish: 'Confirmați nepublicarea', confirmVersionRestoration: 'Confirmați restaurarea versiunii', currentDocumentStatus: 'Documentul {{docStatus}} curent', currentDraft: 'Proiectul Actual', + currentlyPublished: 'Publicat în prezent', + currentlyViewing: 'Vizualizare curentă', currentPublishedVersion: 'Versiunea Publicată Curentă', draft: 'Proiect', draftSavedSuccessfully: 'Proiect salvat cu succes.', lastSavedAgo: 'Ultima salvare acum {{distance}}', modifiedOnly: 'Modificat doar', + moreVersions: 'Mai multe versiuni...', noFurtherVersionsFound: 'Nu s-au găsit alte versiuni', noRowsFound: 'Nu s-a găsit niciun {{label}}', noRowsSelected: 'Niciun {{etichetă}} selectat', preview: 'Previzualizare', previouslyPublished: 'Publicat anterior', + previousVersion: 'Versiune Anterioară', problemRestoringVersion: 'A existat o problemă la restaurarea acestei versiuni', publish: 'Publicați', publishAllLocales: 'Publicați toate configurările regionale și lingvistice', @@ -543,10 +549,12 @@ export const roTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Selectați o versiune pentru a compara', showingVersionsFor: 'Se afișează versiuni pentru:', showLocales: 'Afișați localitățile:', + specificVersion: 'Versiunea specifică', status: 'Status', unpublish: 'Dezpublicare', unpublishing: 'Dezpublicare...', version: 'Versiune', + versionAgo: '{{distance}} în urmă', versionCount_many: '{{count}} versiuni găsite', versionCount_none: 'Nici o versiune găsită', versionCount_one: '{{count}} versiune găsită', diff --git a/packages/translations/src/languages/rs.ts b/packages/translations/src/languages/rs.ts index 394f5e03c..b496e0268 100644 --- a/packages/translations/src/languages/rs.ts +++ b/packages/translations/src/languages/rs.ts @@ -495,22 +495,28 @@ export const rsTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} promenjeno polje', changedFieldsCount_other: '{{count}} promenjena polja', compareVersion: 'Упореди верзију са:', + compareVersions: 'Uporedi verzije', + comparingAgainst: 'Upoređivanje sa', confirmPublish: 'Потврди објаву', confirmRevertToSaved: 'Потврдите враћање на сачувано', confirmUnpublish: 'Потврдите поништавање објаве', confirmVersionRestoration: 'Потврдите враћање верзије', currentDocumentStatus: 'Тренутни {{docStatus}} документа', currentDraft: 'Trenutni nacrt', + currentlyPublished: 'Trenutno objavljeno', + currentlyViewing: 'Trenutno gledate', currentPublishedVersion: 'Trenutno Objavljena Verzija', draft: 'Нацрт', draftSavedSuccessfully: 'Нацрт успешно сачуван.', lastSavedAgo: 'Задњи пут сачувано пре {{distance}', modifiedOnly: 'Samo izmenjen', + moreVersions: 'Više verzija...', noFurtherVersionsFound: 'Нису пронађене наредне верзије', noRowsFound: '{{label}} није пронађено', noRowsSelected: 'Nije odabrana {{label}}', preview: 'Преглед', previouslyPublished: 'Prethodno objavljeno', + previousVersion: 'Prethodna verzija', problemRestoringVersion: 'Настао је проблем при враћању ове верзије', publish: 'Објавити', publishAllLocales: 'Objavi sve lokalitete', @@ -531,10 +537,12 @@ export const rsTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Одаберите верзију за упоређивање', showingVersionsFor: 'Показујем верзије за:', showLocales: 'Прикажи језике:', + specificVersion: 'Specifična verzija', status: 'Статус', unpublish: 'Поништи објаву', unpublishing: 'Поништавање објаве...', version: 'Верзија', + versionAgo: 'pre {{distance}}', versionCount_many: '{{count}} пронађених верзија', versionCount_none: 'Нема пронађених верзија', versionCount_one: '{{count}} пронађена верзија', diff --git a/packages/translations/src/languages/rsLatin.ts b/packages/translations/src/languages/rsLatin.ts index c3df336fd..c0f97d784 100644 --- a/packages/translations/src/languages/rsLatin.ts +++ b/packages/translations/src/languages/rsLatin.ts @@ -497,22 +497,28 @@ export const rsLatinTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} promenjeno polje', changedFieldsCount_other: '{{count}} promenjenih polja', compareVersion: 'Uporedi verziju sa:', + compareVersions: 'Uporedite verzije', + comparingAgainst: 'Upoređivanje sa', confirmPublish: 'Potvrdi objavu', confirmRevertToSaved: 'Potvrdite vraćanje na sačuvano', confirmUnpublish: 'Potvrdite poništavanje objave', confirmVersionRestoration: 'Potvrdite vraćanje verzije', currentDocumentStatus: 'Trenutni {{docStatus}} dokumenta', currentDraft: 'Trenutni nacrt', + currentlyPublished: 'Trenutno Objavljeno', + currentlyViewing: 'Trenutno gledate', currentPublishedVersion: 'Trenutna Objavljena Verzija', draft: 'Nacrt', draftSavedSuccessfully: 'Nacrt uspešno sačuvan.', lastSavedAgo: 'Zadnji put sačuvano pre {{distance}', modifiedOnly: 'Samo izmenjen', + moreVersions: 'Više verzija...', noFurtherVersionsFound: 'Nisu pronađene naredne verzije', noRowsFound: '{{label}} nije pronađeno', noRowsSelected: 'Nije odabrana {{label}}', preview: 'Pregled', previouslyPublished: 'Prethodno objavljeno', + previousVersion: 'Prethodna Verzija', problemRestoringVersion: 'Nastao je problem pri vraćanju ove verzije', publish: 'Objaviti', publishAllLocales: 'Objavi sve lokalne postavke', @@ -533,10 +539,12 @@ export const rsLatinTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Odaberite verziju za upoređivanje', showingVersionsFor: 'Pokazujem verzije za:', showLocales: 'Prikaži jezike:', + specificVersion: 'Specifična verzija', status: 'Status', unpublish: 'Poništi objavu', unpublishing: 'Poništavanje objave...', version: 'Verzija', + versionAgo: 'pre {{distance}}', versionCount_many: '{{count}} pronađenih verzija', versionCount_none: 'Nema pronađenih verzija', versionCount_one: '{{count}} pronađena verzija', diff --git a/packages/translations/src/languages/ru.ts b/packages/translations/src/languages/ru.ts index eece418ec..0fa1df6c8 100644 --- a/packages/translations/src/languages/ru.ts +++ b/packages/translations/src/languages/ru.ts @@ -502,22 +502,28 @@ export const ruTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} изменил поле', changedFieldsCount_other: '{{count}} измененных полей', compareVersion: 'Сравнить версию с:', + compareVersions: 'Сравнить версии', + comparingAgainst: 'Сравнивая с', confirmPublish: 'Подтвердить публикацию', confirmRevertToSaved: 'Подтвердить возврат к сохраненному', confirmUnpublish: 'Подтвердить отмену публикации', confirmVersionRestoration: 'Подтвердить восстановление версии', currentDocumentStatus: 'Текущий статус {{docStatus}} документа', currentDraft: 'Текущий проект', + currentlyPublished: 'В настоящее время опубликовано', + currentlyViewing: 'В настоящее время просматривается', currentPublishedVersion: 'Текущая опубликованная версия', draft: 'Черновик', draftSavedSuccessfully: 'Черновик успешно сохранен.', lastSavedAgo: 'Последний раз сохранено {{distance}} назад', modifiedOnly: 'Модифицирован только', + moreVersions: 'Больше версий...', noFurtherVersionsFound: 'Другие версии не найдены', noRowsFound: 'Не найдено {{label}}', noRowsSelected: 'Не выбран {{label}}', preview: 'Предпросмотр', previouslyPublished: 'Ранее опубликовано', + previousVersion: 'Предыдущая версия', problemRestoringVersion: 'Возникла проблема с восстановлением этой версии', publish: 'Публиковать', publishAllLocales: 'Опубликовать все локали', @@ -538,10 +544,12 @@ export const ruTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Выбрать версию для сравнения', showingVersionsFor: 'Показаны версии для:', showLocales: 'Показать локали:', + specificVersion: 'Конкретная версия', status: 'Статус', unpublish: 'Отменить публикацию', unpublishing: 'Отмена публикации...', version: 'Версия', + versionAgo: '{{distance}} назад', versionCount_many: '{{count}} версий найдено', versionCount_none: 'Версий не найдено', versionCount_one: '{{count}} версия найдена', diff --git a/packages/translations/src/languages/sk.ts b/packages/translations/src/languages/sk.ts index 6b2f4e46e..a99d63999 100644 --- a/packages/translations/src/languages/sk.ts +++ b/packages/translations/src/languages/sk.ts @@ -498,22 +498,28 @@ export const skTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} zmenené pole', changedFieldsCount_other: '{{count}} zmenených polí', compareVersion: 'Porovnať verziu s:', + compareVersions: 'Porovnať verzie', + comparingAgainst: 'Porovnávajúc s', confirmPublish: 'Potvrdiť publikovanie', confirmRevertToSaved: 'Potvrdiť vrátenie k uloženému', confirmUnpublish: 'Potvrdiť zrušenie publikovania', confirmVersionRestoration: 'Potvrdiť obnovenie verzie', currentDocumentStatus: 'Súčasný {{docStatus}} dokument', currentDraft: 'Aktuálny koncept', + currentlyPublished: 'Aktuálne publikované', + currentlyViewing: 'Práve prezeráte', currentPublishedVersion: 'Aktuálne publikovaná verzia', draft: 'Návrh', draftSavedSuccessfully: 'Návrh úspešne uložený.', lastSavedAgo: 'Naposledy uložené pred {{distance}}', modifiedOnly: 'Iba upravené', + moreVersions: 'Viac verzií...', noFurtherVersionsFound: 'Nenájdené ďalšie verzie', noRowsFound: 'Nenájdené {{label}}', noRowsSelected: 'Nie je vybraté žiadne {{označenie}}', preview: 'Náhľad', previouslyPublished: 'Predtým publikované', + previousVersion: 'Predchádzajúca verzia', problemRestoringVersion: 'Pri obnovovaní tejto verzie došlo k problému', publish: 'Publikovať', publishAllLocales: 'Publikujte všetky lokality', @@ -534,10 +540,12 @@ export const skTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Vybrať verziu na porovnanie', showingVersionsFor: 'Zobrazujú sa verzie pre:', showLocales: 'Zobraziť lokálne verzie:', + specificVersion: 'Špecifická verzia', status: 'Stav', unpublish: 'Zrušiť publikovanie', unpublishing: 'Zrušujem publikovanie...', version: 'Verzia', + versionAgo: 'pred {{distance}}', versionCount_many: '{{count}} verzií nájdených', versionCount_none: 'Žiadne verzie nenájdené', versionCount_one: '{{count}} verzia nájdená', diff --git a/packages/translations/src/languages/sl.ts b/packages/translations/src/languages/sl.ts index efc0d494b..b18af6dfb 100644 --- a/packages/translations/src/languages/sl.ts +++ b/packages/translations/src/languages/sl.ts @@ -496,22 +496,28 @@ export const slTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} spremenjeno polje', changedFieldsCount_other: '{{count}} spremenjena polja', compareVersion: 'Primerjaj različico z:', + compareVersions: 'Primerjaj različice', + comparingAgainst: 'Primerjava z', confirmPublish: 'Potrdi objavo', confirmRevertToSaved: 'Potrdi vrnitev na shranjeno', confirmUnpublish: 'Potrdi umik objave', confirmVersionRestoration: 'Potrdi obnovitev različice', currentDocumentStatus: 'Trenutni {{docStatus}} dokument', currentDraft: 'Trenutni osnutek', + currentlyPublished: 'Trenutno objavljeno', + currentlyViewing: 'Trenutno pregledujete', currentPublishedVersion: 'Trenutna objavljena različica', draft: 'Osnutek', draftSavedSuccessfully: 'Osnutek uspešno shranjen.', lastSavedAgo: 'Nazadnje shranjeno pred {{distance}}', modifiedOnly: 'Samo spremenjeno', + moreVersions: 'Več različic...', noFurtherVersionsFound: 'Ni najdenih nadaljnjih različic', noRowsFound: 'Ni najdenih {{label}}', noRowsSelected: 'Ni izbranih {{label}}', preview: 'Predogled', previouslyPublished: 'Predhodno objavljeno', + previousVersion: 'Prejšnja različica', problemRestoringVersion: 'Pri obnavljanju te različice je prišlo do težave', publish: 'Objavi', publishAllLocales: 'Objavi vse jezike', @@ -532,10 +538,12 @@ export const slTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Izberite različico za primerjavo', showingVersionsFor: 'Prikaz različic za:', showLocales: 'Prikaži jezike:', + specificVersion: 'Specifična različica', status: 'Status', unpublish: 'Razveljavi objavo', unpublishing: 'Razveljavljanje objave...', version: 'Različica', + versionAgo: 'pred {{distance}}', versionCount_many: 'Najdenih {{count}} različic', versionCount_none: 'Ni najdenih različic', versionCount_one: 'Najdena {{count}} različica', diff --git a/packages/translations/src/languages/sv.ts b/packages/translations/src/languages/sv.ts index 529c9d4f9..1fef01d54 100644 --- a/packages/translations/src/languages/sv.ts +++ b/packages/translations/src/languages/sv.ts @@ -499,22 +499,28 @@ export const svTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} ändrat fält', changedFieldsCount_other: '{{count}} ändrade fält', compareVersion: 'Jämför version med:', + compareVersions: 'Jämför versioner', + comparingAgainst: 'Jämför mot', confirmPublish: 'Bekräfta publicering', confirmRevertToSaved: 'Bekräfta återgång till sparad version', confirmUnpublish: 'Bekräfta avpublicering', confirmVersionRestoration: 'Bekräfta versionsåterställning', currentDocumentStatus: 'Nuvarande {{docStatus}} dokument', currentDraft: 'Nuvarande utkast', + currentlyPublished: 'För närvarande publicerad', + currentlyViewing: 'Visar för tillfället', currentPublishedVersion: 'Aktuell publicerad version', draft: 'Utkast', draftSavedSuccessfully: 'Utkastet sparades', lastSavedAgo: 'Senast sparad för {{distance}} sedan', modifiedOnly: 'Endast modifierad', + moreVersions: 'Fler versioner...', noFurtherVersionsFound: 'Inga fler versioner hittades', noRowsFound: 'Inga {{label}} hittades', noRowsSelected: 'Inget {{etikett}} valt', preview: 'Förhandsgranska', previouslyPublished: 'Tidigare publicerad', + previousVersion: 'Föregående version', problemRestoringVersion: 'Det uppstod ett problem när den här versionen skulle återställas', publish: 'Publicera', publishAllLocales: 'Publicera alla språk', @@ -535,10 +541,12 @@ export const svTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Välj en version att jämföra', showingVersionsFor: 'Visar versioner för:', showLocales: 'Visa språk:', + specificVersion: 'Specifik version', status: 'Status', unpublish: 'Avpublicera', unpublishing: 'Avpublicerar...', version: 'Version', + versionAgo: '{{distance}} sedan', versionCount_many: '{{count}} versioner hittades', versionCount_none: 'Inga versioner hittades', versionCount_one: '{{count}} version hittades', diff --git a/packages/translations/src/languages/th.ts b/packages/translations/src/languages/th.ts index 9e4fc4c28..36894f940 100644 --- a/packages/translations/src/languages/th.ts +++ b/packages/translations/src/languages/th.ts @@ -488,22 +488,28 @@ export const thTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} เปลี่ยนฟิลด์', changedFieldsCount_other: '{{count}} ฟิลด์ที่มีการเปลี่ยนแปลง', compareVersion: 'เปรียบเทียบเวอร์ชันกับ:', + compareVersions: 'เปรียบเทียบรุ่น', + comparingAgainst: 'เปรียบเทียบกับ', confirmPublish: 'ยืนยันการเผยแพร่', confirmRevertToSaved: 'ยืนยันย้อนการแก้ไข', confirmUnpublish: 'ยืนยันการยกเลิกการเผยแพร่', confirmVersionRestoration: 'ยืนยันการกู้คืนเวอร์ชัน', currentDocumentStatus: 'เอกสารปัจจุบัน', currentDraft: 'ร่างปัจจุบัน', + currentlyPublished: 'ปัจจุบันได้รับการเผยแพร่', + currentlyViewing: 'กำลังดูอยู่ในขณะนี้', currentPublishedVersion: 'เวอร์ชันที่เผยแพร่ในปัจจุบัน', draft: 'ฉบับร่าง', draftSavedSuccessfully: 'บันทึกร่างสำเร็จ', lastSavedAgo: 'บันทึกครั้งล่าสุด {{distance}} ที่ผ่านมา', modifiedOnly: 'แก้ไขเท่านั้น', + moreVersions: 'เพิ่มเวอร์ชั่น...', noFurtherVersionsFound: 'ไม่พบเวอร์ชันอื่น ๆ', noRowsFound: 'ไม่พบ {{label}}', noRowsSelected: 'ไม่มี {{label}} ที่ถูกเลือก', preview: 'ตัวอย่าง', previouslyPublished: 'เผยแพร่ก่อนหน้านี้', + previousVersion: 'เวอร์ชันก่อนหน้านี้', problemRestoringVersion: 'เกิดปัญหาระหว่างการกู้คืนเวอร์ชันนี้', publish: 'เผยแพร่', publishAllLocales: 'เผยแพร่ทุกสถานที่', @@ -524,10 +530,12 @@ export const thTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'เลือกเวอร์ชันที่ต้องการเปรียบเทียบ', showingVersionsFor: 'กำลังแสดงเวอร์ชันของ:', showLocales: 'แสดงภาษา:', + specificVersion: 'เวอร์ชันเฉพาะ', status: 'สถานะ', unpublish: 'หยุดเผยแพร่', unpublishing: 'กำลังหยุดการเผยแพร่...', version: 'เวอร์ชัน', + versionAgo: '{{distance}} ที่แล้ว', versionCount_many: 'พบ {{count}} เวอร์ชัน', versionCount_none: 'ไม่พบเวอร์ชันอื่น', versionCount_one: 'พบ {{count}} เวอร์ชัน', diff --git a/packages/translations/src/languages/tr.ts b/packages/translations/src/languages/tr.ts index c715c2dc2..150f64994 100644 --- a/packages/translations/src/languages/tr.ts +++ b/packages/translations/src/languages/tr.ts @@ -501,22 +501,28 @@ export const trTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} alanı değişti', changedFieldsCount_other: '{{count}} değişen alan', compareVersion: 'Sürümü şununla karşılaştır:', + compareVersions: 'Sürümleri Karşılaştır', + comparingAgainst: 'Karşılaştırma', confirmPublish: 'Yayınlamayı onayla', confirmRevertToSaved: 'Confirm revert to saved', confirmUnpublish: 'Yayından kaldırmayı onayla', confirmVersionRestoration: 'Sürümü Geri Getirmeyi Onayla', currentDocumentStatus: 'Şu an {{docStatus}} döküman', currentDraft: 'Mevcut Taslak', + currentlyPublished: 'Şu Anda Yayınlanmaktadır', + currentlyViewing: 'Şu anda görüntüleniyor', currentPublishedVersion: 'Mevcut Yayınlanan Sürüm', draft: 'Taslak', draftSavedSuccessfully: 'Taslak başarıyla kaydedildi.', lastSavedAgo: 'Son kaydedildi {{distance}} önce', modifiedOnly: 'Yalnızca değiştirilmiş', + moreVersions: 'Daha fazla versiyon...', noFurtherVersionsFound: 'Başka sürüm bulunamadı.', noRowsFound: '{{label}} bulunamadı', noRowsSelected: 'Seçilen {{label}} yok', preview: 'Önizleme', previouslyPublished: 'Daha Önce Yayınlanmış', + previousVersion: 'Önceki Sürüm', problemRestoringVersion: 'Bu sürüme geri döndürürken bir hatayla karşılaşıldı.', publish: 'Yayınla', publishAllLocales: 'Tüm yerel ayarları yayınla', @@ -537,10 +543,12 @@ export const trTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Karşılaştırılacak bir sürüm seçin', showingVersionsFor: 'Şunun için sürümler gösteriliyor:', showLocales: 'Yerel ayarları göster:', + specificVersion: 'Belirli Sürüm', status: 'Durum', unpublish: 'Yayından Kaldır', unpublishing: 'Yayından kaldırılıyor...', version: 'Sürüm', + versionAgo: '{{distance}} önce', versionCount_many: '{{count}} sürüm bulundu', versionCount_none: 'Sürüm bulunamadı', versionCount_one: '{{count}} sürüm bulundu', diff --git a/packages/translations/src/languages/uk.ts b/packages/translations/src/languages/uk.ts index 241326b52..4b1ec9a8a 100644 --- a/packages/translations/src/languages/uk.ts +++ b/packages/translations/src/languages/uk.ts @@ -497,22 +497,28 @@ export const ukTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} змінене поле', changedFieldsCount_other: '{{count}} змінених полів', compareVersion: 'Порівняти версію з:', + compareVersions: 'Порівняти версії', + comparingAgainst: 'Порівнюючи з', confirmPublish: 'Підтвердити публікацію', confirmRevertToSaved: 'Підтвердити повернення до збереженого стану', confirmUnpublish: 'Підвтердити скасування публікації', confirmVersionRestoration: 'Підтвердити відновлення версії', currentDocumentStatus: 'Поточний статус {{docStatus}} документа', currentDraft: 'Поточний проект', + currentlyPublished: 'Наразі опубліковано', + currentlyViewing: 'Поточний перегляд', currentPublishedVersion: 'Поточна опублікована версія', draft: 'Чернетка', draftSavedSuccessfully: 'Чернетку успішно збережено.', lastSavedAgo: 'Востаннє збережено {{distance}} тому', modifiedOnly: 'Модифіковано тільки', + moreVersions: 'Більше версій...', noFurtherVersionsFound: 'Інших версій не знайдено', noRowsFound: 'Не знайдено {{label}}', noRowsSelected: 'Не вибрано {{label}}', preview: 'Попередній перегляд', previouslyPublished: 'Раніше опубліковано', + previousVersion: 'Попередня версія', problemRestoringVersion: 'Виникла проблема з відновленням цієї версії', publish: 'Опублікувати', publishAllLocales: 'Опублікуйте всі локалі', @@ -533,10 +539,12 @@ export const ukTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Оберіть версію для порівняння', showingVersionsFor: 'Показані версії для:', showLocales: 'Показати локалі:', + specificVersion: 'Специфічна версія', status: 'Статус', unpublish: 'Скасувати публікацію', unpublishing: 'Скасування публікації...', version: 'Версія', + versionAgo: '{{distance}} тому', versionCount_many: '{{count}} версій знайдено', versionCount_none: 'Версій не знайдено', versionCount_one: '{{count}} версія знайдена', diff --git a/packages/translations/src/languages/vi.ts b/packages/translations/src/languages/vi.ts index 6c78e7b8c..46485094a 100644 --- a/packages/translations/src/languages/vi.ts +++ b/packages/translations/src/languages/vi.ts @@ -494,22 +494,28 @@ export const viTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} đã thay đổi trường', changedFieldsCount_other: '{{count}} trường đã thay đổi', compareVersion: 'So sánh phiên bản này với:', + compareVersions: 'So sánh các phiên bản', + comparingAgainst: 'So sánh với', confirmPublish: 'Xác nhận xuất bản', confirmRevertToSaved: 'Xác nhận, quay về trạng thái đã lưu', confirmUnpublish: 'Xác nhận, ngưng xuất bản', confirmVersionRestoration: 'Xác nhận, khôi phục về phiên bản trước', currentDocumentStatus: 'Trạng thái tài liệu hiện tại: {{docStatus}}', currentDraft: 'Bản thảo hiện tại', + currentlyPublished: 'Hiện đã xuất bản', + currentlyViewing: 'Đang xem', currentPublishedVersion: 'Phiên bản Đã Xuất bản Hiện tại', draft: 'Bản nháp', draftSavedSuccessfully: 'Bản nháp đã được lưu thành công.', lastSavedAgo: 'Lần lưu cuối cùng {{distance}} trước đây', modifiedOnly: 'Chỉ được sửa đổi', + moreVersions: 'Thêm phiên bản...', noFurtherVersionsFound: 'Không tìm thấy phiên bản cũ hơn', noRowsFound: 'Không tìm thấy: {{label}}', noRowsSelected: 'Không có {{label}} được chọn', preview: 'Bản xem trước', previouslyPublished: 'Đã xuất bản trước đây', + previousVersion: 'Phiên bản Trước', problemRestoringVersion: 'Đã xảy ra vấn đề khi khôi phục phiên bản này', publish: 'Công bố', publishAllLocales: 'Xuất bản tất cả địa phương', @@ -530,10 +536,12 @@ export const viTranslations: DefaultTranslationsObject = { selectVersionToCompare: 'Chọn phiên bản để so sánh', showingVersionsFor: 'Hiển thị các phiên bản cho:', showLocales: 'Hiển thị mã khu vực:', + specificVersion: 'Phiên bản cụ thể', status: 'Trạng thái', unpublish: 'Ẩn tài liệu', unpublishing: 'Đang ẩn tài liệu...', version: 'Phiên bản', + versionAgo: '{{distance}} trước', versionCount_many: '{{count}} phiên bản được tìm thấy', versionCount_none: 'Không có phiên bản nào được tìm thấy', versionCount_one: '{{count}} phiên bản được tìm thấy', diff --git a/packages/translations/src/languages/zh.ts b/packages/translations/src/languages/zh.ts index 52fd471ed..3efbc28b9 100644 --- a/packages/translations/src/languages/zh.ts +++ b/packages/translations/src/languages/zh.ts @@ -476,22 +476,28 @@ export const zhTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}}个字段已更改', changedFieldsCount_other: '{{count}}个字段已更改', compareVersion: '对比版本:', + compareVersions: '比较版本', + comparingAgainst: '与之比较', confirmPublish: '确认发布', confirmRevertToSaved: '确认恢复到保存状态', confirmUnpublish: '确认取消发布', confirmVersionRestoration: '确认版本恢复', currentDocumentStatus: '当前{{docStatus}}文档', currentDraft: '当前草稿', + currentlyPublished: '当前已发布', + currentlyViewing: '当前正在查看', currentPublishedVersion: '当前发布的版本', draft: '草稿', draftSavedSuccessfully: '草稿成功保存。', lastSavedAgo: '上次保存{{distance}}之前', modifiedOnly: '仅修改过的', + moreVersions: '更多版本...', noFurtherVersionsFound: '没有发现其他版本', noRowsFound: '没有发现{{label}}', noRowsSelected: '未选择{{label}}', preview: '预览', previouslyPublished: '先前发布过的', + previousVersion: '以前的版本', problemRestoringVersion: '恢复这个版本时发生了问题', publish: '发布', publishAllLocales: '发布所有语言环境', @@ -512,10 +518,12 @@ export const zhTranslations: DefaultTranslationsObject = { selectVersionToCompare: '选择要比较的版本', showingVersionsFor: '显示版本为:', showLocales: '显示语言环境:', + specificVersion: '特定版本', status: '状态', unpublish: '取消发布', unpublishing: '取消发布中...', version: '版本', + versionAgo: '{{distance}}前', versionCount_many: '发现{{count}}版本', versionCount_none: '没有发现任何版本', versionCount_one: '找到{{count}}版本', diff --git a/packages/translations/src/languages/zhTw.ts b/packages/translations/src/languages/zhTw.ts index e97019063..1ff5d396c 100644 --- a/packages/translations/src/languages/zhTw.ts +++ b/packages/translations/src/languages/zhTw.ts @@ -476,22 +476,28 @@ export const zhTwTranslations: DefaultTranslationsObject = { changedFieldsCount_one: '{{count}} 更改了字段', changedFieldsCount_other: '{{count}}個已更改的欄位', compareVersion: '對比版本:', + compareVersions: '比較版本', + comparingAgainst: '相對於', confirmPublish: '確認發佈', confirmRevertToSaved: '確認回復到儲存狀態', confirmUnpublish: '確認取消發佈', confirmVersionRestoration: '確認版本回復', currentDocumentStatus: '目前{{docStatus}}文件', currentDraft: '目前的草稿', + currentlyPublished: '目前已發布', + currentlyViewing: '目前正在查看', currentPublishedVersion: '目前已發布的版本', draft: '草稿', draftSavedSuccessfully: '草稿儲存成功。', lastSavedAgo: '上次儲存在{{distance}}之前', modifiedOnly: '僅修改過的', + moreVersions: '更多版本...', noFurtherVersionsFound: '沒有發現其他版本', noRowsFound: '沒有發現{{label}}', noRowsSelected: '未選擇 {{label}}', preview: '預覽', previouslyPublished: '先前出版過的', + previousVersion: '先前版本', problemRestoringVersion: '回復這個版本時發生了問題', publish: '發佈', publishAllLocales: '發布所有地區設定', @@ -512,10 +518,12 @@ export const zhTwTranslations: DefaultTranslationsObject = { selectVersionToCompare: '選擇要比較的版本', showingVersionsFor: '顯示版本為:', showLocales: '顯示語言:', + specificVersion: '特定版本', status: '狀態', unpublish: '取消發佈', unpublishing: '取消發佈中...', version: '版本', + versionAgo: '{{distance}}前', versionCount_many: '發現 {{count}}個版本', versionCount_none: '沒有發現任何版本', versionCount_one: '找到 {{count}} 個版本', diff --git a/packages/translations/src/utilities/getTranslation.ts b/packages/translations/src/utilities/getTranslation.ts index 4dce7b926..82fd8312f 100644 --- a/packages/translations/src/utilities/getTranslation.ts +++ b/packages/translations/src/utilities/getTranslation.ts @@ -11,6 +11,9 @@ type LabelType = export const getTranslation = ( label: T, + /** + * @todo type as I18nClient in 4.0 + */ i18n: Pick, 'fallbackLanguage' | 'language' | 't'>, ): T extends JSX.Element ? JSX.Element : string => { // If it's a Record, look for translation. If string or React Element, pass through @@ -35,7 +38,7 @@ export const getTranslation = ( } if (typeof label === 'function') { - return label({ i18n: undefined as any, t: i18n.t }) as unknown as T extends JSX.Element + return label({ i18n: i18n as I18nClient, t: i18n.t }) as unknown as T extends JSX.Element ? JSX.Element : string } diff --git a/packages/translations/src/utilities/languages.ts b/packages/translations/src/utilities/languages.ts index 5c379d51d..39b719437 100644 --- a/packages/translations/src/utilities/languages.ts +++ b/packages/translations/src/utilities/languages.ts @@ -10,6 +10,8 @@ export const acceptedLanguages = [ 'bn-IN', 'ca', 'cs', + 'bn-BD', + 'bn-IN', 'da', 'de', 'en', @@ -54,8 +56,6 @@ export const acceptedLanguages = [ * 'as', * 'az-latin', * 'be', - * 'bn-BD', - * 'bn-IN', * 'bs', * 'ca-ES-valencia', * 'cy', diff --git a/packages/ui/src/elements/Button/index.scss b/packages/ui/src/elements/Button/index.scss index 384ea4c27..683bc3cba 100644 --- a/packages/ui/src/elements/Button/index.scss +++ b/packages/ui/src/elements/Button/index.scss @@ -284,6 +284,25 @@ } } + &--size-xsmall { + --btn-icon-size: calc(var(--base) * 0.8); + // --btn-icon-padding: 0px; // This will be needed when we make icons go edge to edge instead of having built in padding in the svg code + --btn-icon-content-gap: calc(var(--base) * 0.2); + --btn-padding-block-start: 0; + --btn-padding-inline-end: calc(var(--base) * 0.3); + --btn-padding-inline-start: calc(var(--base) * 0.3); + --btn-padding-block-end: 0; + &.btn--icon-position-left { + --btn-padding-inline-start: calc(var(--base) * 0.2); + } + &.btn--icon-position-right { + --btn-padding-inline-end: calc(var(--base) * 0.2); + } + &.btn--icon-style-with-border { + // --btn-icon-padding: 0px; // This will be needed when we make icons go edge to edge instead of having built in padding in the svg code + } + } + &--size-medium { // --btn-icon-padding: 0px; --btn-icon-size: calc(var(--base) * 1.2); diff --git a/packages/ui/src/elements/Button/types.ts b/packages/ui/src/elements/Button/types.ts index 2d514ab31..e7425857b 100644 --- a/packages/ui/src/elements/Button/types.ts +++ b/packages/ui/src/elements/Button/types.ts @@ -52,7 +52,7 @@ export type Props = { ref?: React.RefObject round?: boolean secondaryActions?: secondaryAction | secondaryAction[] - size?: 'large' | 'medium' | 'small' + size?: 'large' | 'medium' | 'small' | 'xsmall' SubMenuPopupContent?: (props: { close: () => void }) => React.ReactNode to?: string tooltip?: string diff --git a/packages/ui/src/elements/ColumnSelector/index.tsx b/packages/ui/src/elements/ColumnSelector/index.tsx index e37fa3009..caffabe9d 100644 --- a/packages/ui/src/elements/ColumnSelector/index.tsx +++ b/packages/ui/src/elements/ColumnSelector/index.tsx @@ -5,15 +5,9 @@ import { fieldIsHiddenOrDisabled, fieldIsID } from 'payload/shared' import React, { useId, useMemo } from 'react' import { FieldLabel } from '../../fields/FieldLabel/index.js' -import { PlusIcon } from '../../icons/Plus/index.js' -import { XIcon } from '../../icons/X/index.js' import { useEditDepth } from '../../providers/EditDepth/index.js' import { useTableColumns } from '../../providers/TableColumns/index.js' -import { DraggableSortable } from '../DraggableSortable/index.js' -import { Pill } from '../Pill/index.js' -import './index.scss' - -const baseClass = 'column-selector' +import { PillSelector, type SelectablePill } from '../PillSelector/index.js' export type Props = { readonly collectionSlug: SanitizedCollectionConfig['slug'] @@ -35,53 +29,48 @@ export const ColumnSelector: React.FC = ({ collectionSlug }) => { [columns], ) - if (!columns) { + const pills: SelectablePill[] = useMemo(() => { + return filteredColumns + ? filteredColumns.map((col, i) => { + const { accessor, active, field } = col + + const label = + 'labelWithPrefix' in field && field.labelWithPrefix !== undefined + ? field.labelWithPrefix + : 'label' in field && field.label !== undefined + ? field.label + : 'name' in field && field.name !== undefined + ? field.name + : undefined + + return { + name: accessor, + key: `${collectionSlug}-${accessor}-${i}${editDepth ? `-${editDepth}-` : ''}${uuid}`, + Label: , + selected: active, + } as SelectablePill + }) + : null + }, [collectionSlug, editDepth, filteredColumns, uuid]) + + if (!pills) { return null } return ( - col?.accessor)} - onDragEnd={({ moveFromIndex, moveToIndex }) => { - void moveColumn({ - fromIndex: moveFromIndex, - toIndex: moveToIndex, - }) + { + void moveColumn({ + fromIndex: moveFromIndex, + toIndex: moveToIndex, + }) + }, }} - > - {filteredColumns.map((col, i) => { - const { accessor, active, field } = col - - const label = - 'labelWithPrefix' in field && field.labelWithPrefix !== undefined - ? field.labelWithPrefix - : 'label' in field && field.label !== undefined - ? field.label - : 'name' in field && field.name !== undefined - ? field.name - : undefined - - return ( - : } - id={accessor} - key={`${collectionSlug}-${accessor}-${i}${editDepth ? `-${editDepth}-` : ''}${uuid}`} - onClick={() => { - void toggleColumn(accessor) - }} - size="small" - > - {col.CustomLabel ?? } - - ) - })} - + onClick={({ pill }) => { + void toggleColumn(pill.name) + }} + pills={pills} + /> ) } diff --git a/packages/ui/src/elements/FieldDiffContainer/index.scss b/packages/ui/src/elements/FieldDiffContainer/index.scss new file mode 100644 index 000000000..60b1265a0 --- /dev/null +++ b/packages/ui/src/elements/FieldDiffContainer/index.scss @@ -0,0 +1,45 @@ +@import '../../scss/styles.scss'; + +@layer payload-default { + .field-diff { + &__locale-label { + background: var(--theme-elevation-100); + border-radius: var(--style-radius-s); + padding: calc(var(--base) * 0.2); + // border-radius: $style-radius-m; + [dir='ltr'] & { + margin-right: calc(var(--base) * 0.25); + } + [dir='rtl'] & { + margin-left: calc(var(--base) * 0.25); + } + } + + &-container { + position: relative; + + // Vertical separator line - not needed anymore, as the parent version view container adds a vertical line that spans the entire height of the container. + /* + &::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: var(--left-offset); + width: 1px; + background-color: var(--theme-elevation-100); + transform: translateX(-50%); // Center the line + }*/ + } + + &-content { + display: grid; + // Need to use 50% 50% so that we can apply overflow-x without the column shrinking to the content width. + // Need -base(0.5) to enure the gap is center aligned - this is required when using 50% over 1fr. + grid-template-columns: calc(50% - base(0.5)) calc(50% - base(0.5)); + gap: base(1); + background: var(--theme-elevation-50); + padding: base(0.5); + } + } +} diff --git a/packages/ui/src/elements/FieldDiffContainer/index.tsx b/packages/ui/src/elements/FieldDiffContainer/index.tsx new file mode 100644 index 000000000..6c9bcc826 --- /dev/null +++ b/packages/ui/src/elements/FieldDiffContainer/index.tsx @@ -0,0 +1,64 @@ +import type { LabelFunction, StaticLabel } from 'payload' + +import './index.scss' + +import { getTranslation, type I18nClient } from '@payloadcms/translations' + +import { FieldDiffLabel } from '../FieldDiffLabel/index.js' + +const baseClass = 'field-diff' + +export const FieldDiffContainer: React.FC<{ + className?: string + From: React.ReactNode + i18n: I18nClient + label: { + label?: false | LabelFunction | StaticLabel + locale?: string + } + nestingLevel?: number + To: React.ReactNode +}> = (args) => { + const { + className, + From, + i18n, + label: { label, locale }, + nestingLevel = 0, + To, + } = args + + return ( +
+ + {locale && {locale}} + {typeof label !== 'function' && getTranslation(label || '', i18n)} + +
+ {From} + {To} +
+
+ ) +} diff --git a/packages/ui/src/elements/FieldDiffLabel/index.scss b/packages/ui/src/elements/FieldDiffLabel/index.scss index 61e50c4cd..6ad0a2905 100644 --- a/packages/ui/src/elements/FieldDiffLabel/index.scss +++ b/packages/ui/src/elements/FieldDiffLabel/index.scss @@ -1,6 +1,11 @@ @layer payload-default { .field-diff-label { - margin-bottom: calc(var(--base) * 0.25); + margin-bottom: calc(var(--base) * 0.35); font-weight: 600; + display: flex; + flex-direction: row; + height: 100%; + align-items: center; + line-height: normal; } } diff --git a/packages/richtext-lexical/src/field/Diff/colors.scss b/packages/ui/src/elements/HTMLDiff/colors.scss similarity index 97% rename from packages/richtext-lexical/src/field/Diff/colors.scss rename to packages/ui/src/elements/HTMLDiff/colors.scss index 290947e21..af5c5f591 100644 --- a/packages/richtext-lexical/src/field/Diff/colors.scss +++ b/packages/ui/src/elements/HTMLDiff/colors.scss @@ -1,4 +1,4 @@ -@import '~@payloadcms/ui/scss'; +@import '../../scss/styles.scss'; @layer payload-default { :root { diff --git a/packages/richtext-lexical/src/field/Diff/htmlDiff/LICENSE.MD b/packages/ui/src/elements/HTMLDiff/diff/LICENSE.MD similarity index 100% rename from packages/richtext-lexical/src/field/Diff/htmlDiff/LICENSE.MD rename to packages/ui/src/elements/HTMLDiff/diff/LICENSE.MD diff --git a/packages/richtext-lexical/src/field/Diff/htmlDiff/index.ts b/packages/ui/src/elements/HTMLDiff/diff/index.ts similarity index 92% rename from packages/richtext-lexical/src/field/Diff/htmlDiff/index.ts rename to packages/ui/src/elements/HTMLDiff/diff/index.ts index a4b1440dd..b6c8a990d 100644 --- a/packages/richtext-lexical/src/field/Diff/htmlDiff/index.ts +++ b/packages/ui/src/elements/HTMLDiff/diff/index.ts @@ -60,6 +60,11 @@ export interface HtmlDiffOptions { * @defaultValue 2 */ minMatchedSize?: number + /** + * Whether to tokenize by character or by word. + * @defaultValue false + */ + tokenizeByCharacter?: boolean } // eslint-disable-next-line regexp/no-super-linear-backtracking, regexp/optimal-quantifier-concatenation @@ -94,6 +99,7 @@ export class HtmlDiff { greedyBoundary = 1000, greedyMatch = true, minMatchedSize = 2, + tokenizeByCharacter = false, }: HtmlDiffOptions = {}, ) { // init config @@ -127,8 +133,9 @@ export class HtmlDiff { } // step1: split HTML to tokens(atomic tokens) - this.oldTokens = this.tokenize(oldHtml) - this.newTokens = this.tokenize(newHtml) + const tokenizeFn = tokenizeByCharacter ? this.tokenizeByCharacter : this.tokenizeByWord + this.oldTokens = tokenizeFn(oldHtml) + this.newTokens = tokenizeFn(newHtml) // step2: find matched blocks this.matchedBlockList = this.getMatchedBlockList() @@ -452,13 +459,52 @@ export class HtmlDiff { return maxSize >= this.config.minMatchedSize ? bestMatchedBlock : null } + /** + * Convert HTML to tokens at character level, preserving HTML tags as complete tokens + * @example + * tokenize(" Hello World ") + * ["", " ", "H", "e", "l", "l", "o", " ", "W", "o", "r", "l", "d", " ", ""] + */ + private tokenizeByCharacter(html: string): string[] { + // First, identify HTML tags and preserve them as complete tokens + const tokens: string[] = [] + let currentPos = 0 + + // Regular expression to match HTML tags (including picture and video tags with content) + const tagRegex = /]*>.*?<\/picture>|]*>.*?<\/video>|<[^>]+>/gs + let match: null | RegExpExecArray + + while ((match = tagRegex.exec(html)) !== null) { + // Add characters before the tag + const beforeTag = html.substring(currentPos, match.index) + if (beforeTag) { + // Split non-tag content into individual characters + for (const char of beforeTag) { + tokens.push(char) + } + } + + // Add the complete tag as a single token + tokens.push(match[0]) + currentPos = match.index + match[0].length + } + + // Add any remaining characters after the last tag + const remaining = html.substring(currentPos) + for (const char of remaining) { + tokens.push(char) + } + + return tokens + } + /** * convert HTML to tokens * @example * tokenize(" Hello World ") * [""," ", "Hello", " ", "World", " ", ""] */ - private tokenize(html: string): string[] { + private tokenizeByWord(html: string): string[] { // atomic token: html tag、continuous numbers or letters、blank spaces、other symbol return ( html.match( diff --git a/packages/ui/src/elements/HTMLDiff/index.scss b/packages/ui/src/elements/HTMLDiff/index.scss new file mode 100644 index 000000000..26647854f --- /dev/null +++ b/packages/ui/src/elements/HTMLDiff/index.scss @@ -0,0 +1,170 @@ +@import '../../scss/styles.scss'; +@import './colors.scss'; + +@layer payload-default { + .html-diff { + font-size: base(0.7); + letter-spacing: 0.02em; + + &-no-value { + color: var(--theme-elevation-400); + } + + pre { + margin-top: 0; + margin-bottom: 0; + } + + // Apply background color to parents that have children with diffs + p, + h1, + h2, + h3, + h4, + h5, + blockquote, + pre, + h6 { + &:not([data-enable-match='false']) { + &:has([data-match-type='create']) { + position: relative; + z-index: 1; + &::before { + content: ''; + position: absolute; + top: -(base(0.5)); + bottom: -(base(0.5)); + left: -10px; + right: -(base(0.5)); + display: block; + background-color: var(--diff-create-parent-bg); + color: var(--diff-create-parent-color); + z-index: -1; /* Place behind the text */ + } + } + + &:has([data-match-type='delete']) { + position: relative; + z-index: 1; + &::before { + content: ''; + position: absolute; + top: -(base(0.5)); + bottom: -(base(0.5)); + left: -(base(0.5)); + right: -10px; + display: block; + background-color: var(--diff-delete-parent-bg); + color: var(--diff-delete-parent-color); + z-index: -1; /* Place behind the text */ + } + } + } + } + + li { + &:not([data-enable-match='false']) { + &:has([data-match-type='create']) { + position: relative; + z-index: 1; + &::before { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: -10px; + right: -(base(0.5)); + display: block; + background-color: var(--diff-create-parent-bg); + color: var(--diff-create-parent-color); + z-index: -1; /* Place behind the text */ + } + } + + &:has([data-match-type='delete']) { + position: relative; + z-index: 1; + &::before { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: -(base(0.5)); + right: -10px; + display: block; + background-color: var(--diff-delete-parent-bg); + color: var(--diff-delete-parent-color); + z-index: -1; /* Place behind the text */ + } + } + } + } + + li::marker { + color: var(--theme-text); + } + + [data-match-type='delete']:not([data-enable-match='false']):not( + :is([data-enable-match='false'] *) + ) { + color: var(--diff-delete-pill-color); + text-decoration-color: var(--diff-delete-pill-color); + text-decoration-line: line-through; + background-color: var(--diff-delete-pill-bg); + border-radius: 4px; + text-decoration-thickness: 1px; + } + + a[data-match-type='delete'] + :not([data-enable-match='false']) + :not(:is([data-enable-match='false'] *)) { + color: var(--diff-delete-link-color); + } + + // :not(img) required to increase specificity + a[data-match-type='create']:not(img) + :not([data-enable-match='false']) + :not(:is([data-enable-match='false'] *)) { + color: var(--diff-create-link-color); + } + + [data-match-type='create']:not(img):not([data-enable-match='false']):not( + :is([data-enable-match='false'] *) + ) { + background-color: var(--diff-create-pill-bg); + color: var(--diff-create-pill-color); + border-radius: 4px; + } + + .html-diff { + &-create-inline-wrapper, + &-delete-inline-wrapper { + display: inline-flex; + } + + &-create-block-wrapper, + &-delete-block-wrapper { + display: flex; + } + + &-create-inline-wrapper, + &-delete-inline-wrapper, + &-create-block-wrapper, + &-delete-block-wrapper { + position: relative; + align-items: center; + flex-direction: row; + + &::after { + position: absolute; + top: 0; + left: 0; + display: block; + width: 100%; + height: 100%; + content: ''; + } + } + } + } +} diff --git a/packages/ui/src/elements/HTMLDiff/index.tsx b/packages/ui/src/elements/HTMLDiff/index.tsx new file mode 100644 index 000000000..bf02fde16 --- /dev/null +++ b/packages/ui/src/elements/HTMLDiff/index.tsx @@ -0,0 +1,41 @@ +import React from 'react' + +import { HtmlDiff } from './diff/index.js' +import './index.scss' + +const baseClass = 'html-diff' + +export const getHTMLDiffComponents = ({ + fromHTML, + toHTML, + tokenizeByCharacter, +}: { + fromHTML: string + toHTML: string + tokenizeByCharacter?: boolean +}): { + From: React.ReactNode + To: React.ReactNode +} => { + const diffHTML = new HtmlDiff(fromHTML, toHTML, { + tokenizeByCharacter, + }) + + const [oldHTML, newHTML] = diffHTML.getSideBySideContents() + + const From = oldHTML ? ( +
+ ) : null + + const To = newHTML ? ( +
+ ) : null + + return { From, To } +} diff --git a/packages/ui/src/elements/ListControls/index.scss b/packages/ui/src/elements/ListControls/index.scss index 4957240e3..6443e5f16 100644 --- a/packages/ui/src/elements/ListControls/index.scss +++ b/packages/ui/src/elements/ListControls/index.scss @@ -34,7 +34,7 @@ gap: calc(var(--base) / 4); } - .column-selector, + .pill-selector, .where-builder, .sort-complex { margin-top: base(1); @@ -82,7 +82,7 @@ width: 100%; } - .column-selector, + .pill-selector, .where-builder, .sort-complex { margin-top: calc(var(--base) / 2); diff --git a/packages/ui/src/elements/Nav/context.tsx b/packages/ui/src/elements/Nav/context.tsx index 61430ff42..de6050ace 100644 --- a/packages/ui/src/elements/Nav/context.tsx +++ b/packages/ui/src/elements/Nav/context.tsx @@ -96,9 +96,12 @@ export const NavProvider: React.FC<{ } setHydrated(true) - setTimeout(() => { + const timeout = setTimeout(() => { setShouldAnimate(true) }, 100) + return () => { + clearTimeout(timeout) + } }, [largeBreak, midBreak, smallBreak]) // when the component unmounts, clear all body scroll locks diff --git a/packages/ui/src/elements/Pill/index.tsx b/packages/ui/src/elements/Pill/index.tsx index e7e5d310a..41a0edc58 100644 --- a/packages/ui/src/elements/Pill/index.tsx +++ b/packages/ui/src/elements/Pill/index.tsx @@ -5,6 +5,16 @@ import React from 'react' // TODO: abstract this out to support all routers import { Link } from '../Link/index.js' +export type PillStyle = + | 'always-white' + | 'dark' + | 'error' + | 'light' + | 'light-gray' + | 'success' + | 'warning' + | 'white' + export type PillProps = { alignIcon?: 'left' | 'right' 'aria-checked'?: boolean @@ -20,15 +30,10 @@ export type PillProps = { icon?: React.ReactNode id?: string onClick?: () => void - pillStyle?: - | 'always-white' - | 'dark' - | 'error' - | 'light' - | 'light-gray' - | 'success' - | 'warning' - | 'white' + /** + * @default 'light' + */ + pillStyle?: PillStyle rounded?: boolean size?: 'medium' | 'small' to?: string diff --git a/packages/ui/src/elements/ColumnSelector/index.scss b/packages/ui/src/elements/PillSelector/index.scss similarity index 84% rename from packages/ui/src/elements/ColumnSelector/index.scss rename to packages/ui/src/elements/PillSelector/index.scss index d310aa1b8..59b9f1636 100644 --- a/packages/ui/src/elements/ColumnSelector/index.scss +++ b/packages/ui/src/elements/PillSelector/index.scss @@ -1,18 +1,18 @@ @import '../../scss/styles.scss'; @layer payload-default { - .column-selector { + .pill-selector { display: flex; flex-wrap: wrap; background: var(--theme-elevation-50); padding: var(--base); gap: calc(var(--base) / 2); - &__column { + &__pill { background-color: transparent; box-shadow: 0 0 0 1px var(--theme-elevation-150); - &.column-selector__column { + &.pill-selector__pill { cursor: pointer; &:hover { @@ -20,7 +20,7 @@ } } - &.column-selector__column--active { + &.pill-selector__pill--selected { background-color: var(--theme-elevation-0); box-shadow: 0 0px 1px 1px var(--theme-elevation-150), @@ -41,7 +41,7 @@ } @include small-break { - padding: calc(var(--base) / 2) calc(var(--base) / 2) 0; + padding: calc(var(--base) / 2); } } } diff --git a/packages/ui/src/elements/PillSelector/index.tsx b/packages/ui/src/elements/PillSelector/index.tsx new file mode 100644 index 000000000..25b04038f --- /dev/null +++ b/packages/ui/src/elements/PillSelector/index.tsx @@ -0,0 +1,81 @@ +'use client' + +import React from 'react' + +import { PlusIcon } from '../../icons/Plus/index.js' +import { XIcon } from '../../icons/X/index.js' +import { DraggableSortable } from '../DraggableSortable/index.js' +import { Pill } from '../Pill/index.js' +import './index.scss' + +const baseClass = 'pill-selector' + +export type SelectablePill = { + key?: string + Label?: React.ReactNode + name: string + selected: boolean +} + +export type Props = { + draggable?: { + onDragEnd: (args: { moveFromIndex: number; moveToIndex: number }) => void + } + onClick?: (args: { pill: SelectablePill }) => Promise | void + pills: SelectablePill[] +} + +/** + * Displays a wrappable list of pills that can be selected or deselected. + * If `draggable` is true, the pills can be reordered by dragging. + */ +export const PillSelector: React.FC = ({ draggable, onClick, pills }) => { + const Wrapper = React.useMemo(() => { + if (draggable) { + return ({ children }) => ( + pill.name)} + onDragEnd={({ moveFromIndex, moveToIndex }) => { + draggable.onDragEnd({ + moveFromIndex, + moveToIndex, + }) + }} + > + {children} + + ) + } else { + return ({ children }) =>
{children}
+ } + }, [draggable, pills]) + + return ( + + {pills.map((pill, i) => { + return ( + : } + id={pill.name} + key={pill.key ?? `${pill.name}-${i}`} + onClick={() => { + if (onClick) { + void onClick({ pill }) + } + }} + size="small" + > + {pill.Label ?? {pill.name}} + + ) + })} + + ) +} diff --git a/packages/ui/src/elements/Popup/PopupTrigger/index.scss b/packages/ui/src/elements/Popup/PopupTrigger/index.scss index 08cab9804..7a6826ed8 100644 --- a/packages/ui/src/elements/Popup/PopupTrigger/index.scss +++ b/packages/ui/src/elements/Popup/PopupTrigger/index.scss @@ -16,6 +16,10 @@ background: transparent; } + &--size-xsmall { + padding: base(0.1); + } + &--size-small { padding: base(0.2); } diff --git a/packages/ui/src/elements/Popup/PopupTrigger/index.tsx b/packages/ui/src/elements/Popup/PopupTrigger/index.tsx index f5b7de0c5..6bf6022ea 100644 --- a/packages/ui/src/elements/Popup/PopupTrigger/index.tsx +++ b/packages/ui/src/elements/Popup/PopupTrigger/index.tsx @@ -13,7 +13,7 @@ export type PopupTriggerProps = { disabled?: boolean noBackground?: boolean setActive: (active: boolean) => void - size?: 'large' | 'medium' | 'small' + size?: 'large' | 'medium' | 'small' | 'xsmall' } export const PopupTrigger: React.FC = (props) => { diff --git a/packages/ui/src/elements/Popup/index.scss b/packages/ui/src/elements/Popup/index.scss index 2d3f80509..640174081 100644 --- a/packages/ui/src/elements/Popup/index.scss +++ b/packages/ui/src/elements/Popup/index.scss @@ -65,6 +65,13 @@ // SIZE //////////////////////////////// + &--size-xsmall { + --popup-width: 80px; + .popup__content { + @include shadow-sm; + } + } + &--size-small { --popup-width: 100px; .popup__content { @@ -90,6 +97,10 @@ /// BUTTON SIZE //////////////////////////////// + &--button-size-xsmall { + --button-size-offset: -12px; + } + &--button-size-small { --button-size-offset: -8px; } diff --git a/packages/ui/src/elements/Popup/index.tsx b/packages/ui/src/elements/Popup/index.tsx index c080c5846..612bba205 100644 --- a/packages/ui/src/elements/Popup/index.tsx +++ b/packages/ui/src/elements/Popup/index.tsx @@ -17,7 +17,7 @@ export type PopupProps = { boundingRef?: React.RefObject button?: React.ReactNode buttonClassName?: string - buttonSize?: 'large' | 'medium' | 'small' + buttonSize?: 'large' | 'medium' | 'small' | 'xsmall' buttonType?: 'custom' | 'default' | 'none' caret?: boolean children?: React.ReactNode diff --git a/packages/ui/src/elements/ReactSelect/ValueContainer/index.scss b/packages/ui/src/elements/ReactSelect/ValueContainer/index.scss index 47901e7ef..8d78b0ab1 100644 --- a/packages/ui/src/elements/ReactSelect/ValueContainer/index.scss +++ b/packages/ui/src/elements/ReactSelect/ValueContainer/index.scss @@ -4,6 +4,14 @@ .value-container { flex-grow: 1; min-width: 0; + display: flex; + align-items: center; + flex-direction: row; + gap: calc(var(--base) / 2); + + &__label { + color: var(--theme-elevation-550); + } .rs__value-container { overflow: visible; diff --git a/packages/ui/src/elements/ReactSelect/ValueContainer/index.tsx b/packages/ui/src/elements/ReactSelect/ValueContainer/index.tsx index ced85a7ed..383df33a4 100644 --- a/packages/ui/src/elements/ReactSelect/ValueContainer/index.tsx +++ b/packages/ui/src/elements/ReactSelect/ValueContainer/index.tsx @@ -16,6 +16,9 @@ export const ValueContainer: React.FC> = (props return (
+ {customProps?.valueContainerLabel && ( + {customProps?.valueContainerLabel} + )}
) diff --git a/packages/ui/src/elements/ReactSelect/types.ts b/packages/ui/src/elements/ReactSelect/types.ts index 58e324b1b..a2a2e7ca9 100644 --- a/packages/ui/src/elements/ReactSelect/types.ts +++ b/packages/ui/src/elements/ReactSelect/types.ts @@ -22,6 +22,7 @@ type CustomSelectProps = { }) => void onDuplicate?: DocumentDrawerProps['onSave'] onSave?: DocumentDrawerProps['onSave'] + valueContainerLabel?: string } // augment the types for the `Select` component from `react-select` diff --git a/packages/ui/src/elements/WhereBuilder/Condition/Relationship/index.tsx b/packages/ui/src/elements/WhereBuilder/Condition/Relationship/index.tsx index c1808359b..96a352641 100644 --- a/packages/ui/src/elements/WhereBuilder/Condition/Relationship/index.tsx +++ b/packages/ui/src/elements/WhereBuilder/Condition/Relationship/index.tsx @@ -374,7 +374,9 @@ export const RelationshipFilter: React.FC = (props) => { return (
- {!errorLoading && ( + {errorLoading ? ( +
{errorLoading}
+ ) : ( = (props) => { value={valueToRender} /> )} - {errorLoading &&
{errorLoading}
}
) } diff --git a/packages/ui/src/exports/client/index.ts b/packages/ui/src/exports/client/index.ts index da847eb64..4131a17f1 100644 --- a/packages/ui/src/exports/client/index.ts +++ b/packages/ui/src/exports/client/index.ts @@ -62,6 +62,8 @@ export { DrawerContentContainer } from '../../elements/DrawerContentContainer/in export type { BulkUploadProps } from '../../elements/BulkUpload/index.js' export { Banner } from '../../elements/Banner/index.js' export { Button } from '../../elements/Button/index.js' +export { AnimateHeight } from '../../elements/AnimateHeight/index.js' +export { PillSelector, type SelectablePill } from '../../elements/PillSelector/index.js' export { Card } from '../../elements/Card/index.js' export { Collapsible, useCollapsible } from '../../elements/Collapsible/index.js' export { CopyLocaleData } from '../../elements/CopyLocaleData/index.js' @@ -70,6 +72,7 @@ export { DeleteMany } from '../../elements/DeleteMany/index.js' export { DocumentControls } from '../../elements/DocumentControls/index.js' export { Dropzone } from '../../elements/Dropzone/index.js' export { documentDrawerBaseClass, useDocumentDrawer } from '../../elements/DocumentDrawer/index.js' +export { getHTMLDiffComponents } from '../../elements/HTMLDiff/index.js' export type { DocumentDrawerProps, DocumentTogglerProps, @@ -277,6 +280,8 @@ export { Warning as WarningIcon } from '../../providers/ToastContainer/icons/War // providers export { + type RenderDocumentResult, + type RenderDocumentServerFunction, ServerFunctionsProvider, useServerFunctions, } from '../../providers/ServerFunctions/index.js' @@ -393,3 +398,5 @@ export { SetDocumentTitle } from '../../views/Edit/SetDocumentTitle/index.js' export { parseSearchParams } from '../../utilities/parseSearchParams.js' export { FieldDiffLabel } from '../../elements/FieldDiffLabel/index.js' +export { FieldDiffContainer } from '../../elements/FieldDiffContainer/index.js' +export { formatTimeToNow } from '../../utilities/formatDocTitle/formatDateTitle.js' diff --git a/packages/ui/src/exports/rsc/index.ts b/packages/ui/src/exports/rsc/index.ts index 53655247e..7e0b00208 100644 --- a/packages/ui/src/exports/rsc/index.ts +++ b/packages/ui/src/exports/rsc/index.ts @@ -1,6 +1,8 @@ +export { FieldDiffContainer } from '../../elements/FieldDiffContainer/index.js' export { FieldDiffLabel } from '../../elements/FieldDiffLabel/index.js' export { FolderTableCell } from '../../elements/FolderView/Cell/index.server.js' export { FolderEditField } from '../../elements/FolderView/Field/index.server.js' +export { getHTMLDiffComponents } from '../../elements/HTMLDiff/index.js' export { File } from '../../graphics/File/index.js' export { CheckIcon } from '../../icons/Check/index.js' export { copyDataFromLocaleHandler } from '../../utilities/copyDataFromLocale.js' diff --git a/packages/ui/src/fields/Checkbox/index.scss b/packages/ui/src/fields/Checkbox/index.scss index 4e930a257..6df8c752e 100644 --- a/packages/ui/src/fields/Checkbox/index.scss +++ b/packages/ui/src/fields/Checkbox/index.scss @@ -23,7 +23,7 @@ } } - label { + label.field-label { padding-bottom: 0; padding-left: base(0.5); } diff --git a/packages/ui/src/fields/FieldLabel/index.tsx b/packages/ui/src/fields/FieldLabel/index.tsx index 24182d197..fe77862a7 100644 --- a/packages/ui/src/fields/FieldLabel/index.tsx +++ b/packages/ui/src/fields/FieldLabel/index.tsx @@ -26,7 +26,9 @@ export const FieldLabel: React.FC = (props) => { const { uuid } = useForm() const editDepth = useEditDepth() + const htmlFor = htmlForFromProps || generateFieldID(path, editDepth, uuid) + const { i18n } = useTranslation() const { code, label: localLabel } = useLocale() @@ -35,7 +37,7 @@ export const FieldLabel: React.FC = (props) => { if (label) { return ( - + {getTranslation(label, i18n)} {required && !unstyled && *} {localized && !hideLocale && ( diff --git a/packages/ui/src/fields/Relationship/Input.tsx b/packages/ui/src/fields/Relationship/Input.tsx index 13242f6fc..6adedd8ac 100644 --- a/packages/ui/src/fields/Relationship/Input.tsx +++ b/packages/ui/src/fields/Relationship/Input.tsx @@ -102,7 +102,6 @@ export const RelationshipInput: React.FC = (props) => { const valueRef = useRef(value) // the line below seems odd - // eslint-disable-next-line react-compiler/react-compiler valueRef.current = value const [DocumentDrawer, , { isDrawerOpen, openDrawer }] = useDocumentDrawer({ @@ -706,7 +705,9 @@ export const RelationshipInput: React.FC = (props) => { Fallback={} /> {BeforeInput} - {!errorLoading && ( + {errorLoading ? ( +
{errorLoading}
+ ) : (
= (props) => { )}
)} - {errorLoading &&
{errorLoading}
} {AfterInput} { - void fetchFullUser() - }, [fetchFullUser]) + void fetchFullUserEvent() + }, []) - // When location changes, refresh cookie - useEffect(() => { + const refreshCookieEvent = useEffectEvent(() => { if (id) { refreshCookie() } - }, [debouncedLocationChange, refreshCookie, id]) + }) + + // When location changes, refresh cookie + useEffect(() => { + refreshCookieEvent() + }, [debouncedLocationChange]) useEffect(() => { setLastLocationChange(Date.now()) diff --git a/packages/ui/src/providers/ServerFunctions/index.tsx b/packages/ui/src/providers/ServerFunctions/index.tsx index c78d98ec9..73530abbc 100644 --- a/packages/ui/src/providers/ServerFunctions/index.tsx +++ b/packages/ui/src/providers/ServerFunctions/index.tsx @@ -1,11 +1,16 @@ import type { + AdminViewServerPropsOnly, BuildFormStateArgs, BuildTableStateArgs, Data, + DocumentPreferences, DocumentSlots, - ErrorResult, + FormState, GetFolderResultsComponentAndDataArgs, Locale, + Params, + RenderDocumentVersionsProperties, + ServerFunction, ServerFunctionClient, } from 'payload' @@ -38,22 +43,44 @@ type GetTableStateClient = ( } & Omit, ) => ReturnType -type RenderDocument = (args: { +export type RenderDocumentResult = { + data: any + Document: React.ReactNode + preferences: DocumentPreferences +} + +type RenderDocumentBaseArgs = { collectionSlug: string disableActions?: boolean - docID?: number | string + docID: number | string drawerSlug?: string initialData?: Data + initialState?: FormState locale?: Locale overrideEntityVisibility?: boolean + paramsOverride?: AdminViewServerPropsOnly['params'] redirectAfterCreate?: boolean - redirectAfterDelete?: boolean - redirectAfterDuplicate?: boolean - signal?: AbortSignal -}) => Promise< - { data: Data; Document: React.ReactNode } | ({ data: never; Document: never } & ErrorResult) + redirectAfterDelete: boolean + redirectAfterDuplicate: boolean + searchParams?: Params + /** + * Properties specific to the versions view + */ + versions?: RenderDocumentVersionsProperties +} + +export type RenderDocumentServerFunction = ServerFunction< + RenderDocumentBaseArgs, + Promise > +type RenderDocumentServerFunctionHookFn = ( + // No req or importMap - those are augmented by handleServerFunctions + args: { + signal?: AbortSignal + } & RenderDocumentBaseArgs, +) => Promise + type CopyDataFromLocaleClient = ( args: { signal?: AbortSignal @@ -77,7 +104,7 @@ type ServerFunctionsContextType = { getFolderResultsComponentAndData: GetFolderResultsComponentAndDataClient getFormState: GetFormStateClient getTableState: GetTableStateClient - renderDocument: RenderDocument + renderDocument: RenderDocumentServerFunctionHookFn schedulePublish: SchedulePublishClient serverFunction: ServerFunctionClient } @@ -136,7 +163,7 @@ export const ServerFunctionsProvider: React.FC<{ error += ` for document with ID ${rest.doc.value} in collection ${rest.doc.relationTo}` } - return { error: '' } + return { error } }, [serverFunction], ) @@ -189,15 +216,17 @@ export const ServerFunctionsProvider: React.FC<{ [serverFunction], ) - const renderDocument = useCallback( + const renderDocument = useCallback( async (args) => { const { signal: remoteSignal, ...rest } = args || {} - try { const result = (await serverFunction({ name: 'render-document', - args: { fallbackLocale: false, ...rest }, - })) as Awaited> // TODO: infer this type when `strictNullChecks` is enabled + args: { + fallbackLocale: false, + ...rest, + } as Parameters[0], + })) as Awaited> // TODO: infer this type when `strictNullChecks` is enabled return result } catch (_err) { diff --git a/packages/ui/src/providers/Translation/index.tsx b/packages/ui/src/providers/Translation/index.tsx index 9e932f21c..01ba55fbd 100644 --- a/packages/ui/src/providers/Translation/index.tsx +++ b/packages/ui/src/providers/Translation/index.tsx @@ -67,12 +67,15 @@ export const TranslationProvider: React.FC = ({ const router = useRouter() const [dateFNS, setDateFNS] = useState() - const nextT: ContextType['t'] = (key, vars): string => - t({ - key, - translations, - vars, - }) + const nextT: ContextType['t'] = React.useCallback( + (key, vars): string => + t({ + key, + translations, + vars, + }), + [translations], + ) const switchLanguage = React.useCallback( async (lang: string) => { diff --git a/packages/ui/src/utilities/buildFormState.ts b/packages/ui/src/utilities/buildFormState.ts index e92d1ca35..5da3b1ac1 100644 --- a/packages/ui/src/utilities/buildFormState.ts +++ b/packages/ui/src/utilities/buildFormState.ts @@ -1,4 +1,11 @@ -import type { BuildFormStateArgs, ClientConfig, ClientUser, ErrorResult, FormState } from 'payload' +import type { + BuildFormStateArgs, + ClientConfig, + ClientUser, + ErrorResult, + FormState, + ServerFunction, +} from 'payload' import { formatErrors } from 'payload' import { getSelectMode, reduceFieldsToValues } from 'payload/shared' @@ -36,9 +43,10 @@ type BuildFormStateErrorResult = { export type BuildFormStateResult = BuildFormStateErrorResult | BuildFormStateSuccessResult -export const buildFormStateHandler = async ( - args: BuildFormStateArgs, -): Promise => { +export const buildFormStateHandler: ServerFunction< + BuildFormStateArgs, + Promise +> = async (args) => { const { req } = args const incomingUserSlug = req.user?.collection diff --git a/packages/ui/src/utilities/buildTableState.ts b/packages/ui/src/utilities/buildTableState.ts index f192e891e..70393ced2 100644 --- a/packages/ui/src/utilities/buildTableState.ts +++ b/packages/ui/src/utilities/buildTableState.ts @@ -7,6 +7,7 @@ import type { ListPreferences, PaginatedDocs, SanitizedCollectionConfig, + ServerFunction, Where, } from 'payload' @@ -41,9 +42,10 @@ type BuildTableStateErrorResult = { export type BuildTableStateResult = BuildTableStateErrorResult | BuildTableStateSuccessResult -export const buildTableStateHandler = async ( - args: BuildTableStateArgs, -): Promise => { +export const buildTableStateHandler: ServerFunction< + BuildTableStateArgs, + Promise +> = async (args) => { const { req } = args try { diff --git a/packages/ui/src/utilities/copyDataFromLocale.ts b/packages/ui/src/utilities/copyDataFromLocale.ts index 7d11a2e5c..ecd65767b 100644 --- a/packages/ui/src/utilities/copyDataFromLocale.ts +++ b/packages/ui/src/utilities/copyDataFromLocale.ts @@ -6,6 +6,7 @@ import { type FlattenedBlock, formatErrors, type PayloadRequest, + type ServerFunction, } from 'payload' import { fieldAffectsData, fieldShouldBeLocalized, tabHasName } from 'payload/shared' @@ -200,7 +201,7 @@ function removeIds(data: Data): Data { return data } -export const copyDataFromLocaleHandler = async (args: CopyDataFromLocaleArgs) => { +export const copyDataFromLocaleHandler: ServerFunction = async (args) => { const { req } = args try { diff --git a/packages/ui/src/utilities/formatDocTitle/formatDateTitle.ts b/packages/ui/src/utilities/formatDocTitle/formatDateTitle.ts index 1fd7a5f43..68677b239 100644 --- a/packages/ui/src/utilities/formatDocTitle/formatDateTitle.ts +++ b/packages/ui/src/utilities/formatDocTitle/formatDateTitle.ts @@ -38,7 +38,7 @@ type FormatTimeToNowArgs = { } export const formatTimeToNow = ({ date, i18n }: FormatTimeToNowArgs): string => { - const theDate = new Date(date) + const theDate = typeof date === 'string' ? new Date(date) : date return i18n?.dateFNS ? formatDistanceToNow(theDate, { locale: i18n.dateFNS }) : `${i18n.t('general:loading')}...` diff --git a/packages/ui/src/utilities/generateFieldID.ts b/packages/ui/src/utilities/generateFieldID.ts index 84c0e015a..e429b804e 100644 --- a/packages/ui/src/utilities/generateFieldID.ts +++ b/packages/ui/src/utilities/generateFieldID.ts @@ -2,6 +2,5 @@ export const generateFieldID = (path: string, editDepth: number, uuid: string) = if (!path) { return undefined } - return `field-${path.replace(/\./g, '__')}${editDepth > 1 ? `-${editDepth}` : ''}${uuid ? `-${uuid}` : ''}` } diff --git a/packages/ui/src/utilities/getFolderResultsComponentAndData.tsx b/packages/ui/src/utilities/getFolderResultsComponentAndData.tsx index 0761f63c4..802eb60b9 100644 --- a/packages/ui/src/utilities/getFolderResultsComponentAndData.tsx +++ b/packages/ui/src/utilities/getFolderResultsComponentAndData.tsx @@ -2,6 +2,7 @@ import type { CollectionSlug, ErrorResult, GetFolderResultsComponentAndDataArgs, + ServerFunction, Where, } from 'payload' import type { FolderBreadcrumb, FolderOrDocument } from 'payload/shared' @@ -31,11 +32,10 @@ type GetFolderResultsComponentAndDataErrorResult = { | ErrorResult ) -export const getFolderResultsComponentAndDataHandler = async ( - args: GetFolderResultsComponentAndDataArgs, -): Promise< - GetFolderResultsComponentAndDataErrorResult | GetFolderResultsComponentAndDataResult -> => { +export const getFolderResultsComponentAndDataHandler: ServerFunction< + GetFolderResultsComponentAndDataArgs, + Promise +> = async (args) => { const { req } = args try { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e0236348..7ba8488e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -734,9 +734,6 @@ importers: qs-esm: specifier: 7.0.2 version: 7.0.2 - react-diff-viewer-continued: - specifier: 4.0.5 - version: 4.0.5(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) sass: specifier: 1.77.4 version: 1.77.4 @@ -3062,9 +3059,6 @@ packages: '@emotion/cache@11.14.0': resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} - '@emotion/css@11.13.5': - resolution: {integrity: sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==} - '@emotion/hash@0.9.2': resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} @@ -6345,9 +6339,6 @@ packages: cjs-module-lexer@1.4.1: resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} - classnames@2.5.1: - resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} - clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -6689,10 +6680,6 @@ packages: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - diff@5.2.0: - resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} - engines: {node: '>=0.3.1'} - dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -9191,13 +9178,6 @@ packages: react: 19.1.0 react-dom: 19.1.0 - react-diff-viewer-continued@4.0.5: - resolution: {integrity: sha512-L43gIPdhHgu1MYdip4vNqAt5s2JLICKe2/RyGUr2ohAxfhYaH1+QZ6vBO0qgo4xGBhE3jmvbOA/swq4/gdS/0g==} - engines: {node: '>= 16'} - peerDependencies: - react: 19.1.0 - react-dom: 19.1.0 - react-dom@19.1.0: resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: @@ -12449,16 +12429,6 @@ snapshots: '@emotion/weak-memoize': 0.4.0 stylis: 4.2.0 - '@emotion/css@11.13.5': - dependencies: - '@emotion/babel-plugin': 11.13.5 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - '@emotion/sheet': 1.4.0 - '@emotion/utils': 1.4.2 - transitivePeerDependencies: - - supports-color - '@emotion/hash@0.9.2': {} '@emotion/memoize@0.9.0': {} @@ -16151,8 +16121,6 @@ snapshots: cjs-module-lexer@1.4.1: {} - classnames@2.5.1: {} - clean-stack@2.2.0: {} cli-cursor@5.0.0: @@ -16456,8 +16424,6 @@ snapshots: diff-sequences@29.6.3: {} - diff@5.2.0: {} - dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -19482,19 +19448,6 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - react-diff-viewer-continued@4.0.5(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): - dependencies: - '@emotion/css': 11.13.5 - '@emotion/react': 11.14.0(@types/react@19.1.0)(react@19.1.0) - classnames: 2.5.1 - diff: 5.2.0 - memoize-one: 6.0.0 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - transitivePeerDependencies: - - '@types/react' - - supports-color - react-dom@19.1.0(react@19.1.0): dependencies: react: 19.1.0 diff --git a/test/admin/e2e/list-view/e2e.spec.ts b/test/admin/e2e/list-view/e2e.spec.ts index 0e7206859..b625945a9 100644 --- a/test/admin/e2e/list-view/e2e.spec.ts +++ b/test/admin/e2e/list-view/e2e.spec.ts @@ -163,7 +163,7 @@ describe('List View', () => { await expect(page.locator('.list-controls__columns.rah-static--height-auto')).toBeVisible() await page - .locator('.column-selector .column-selector__column', { + .locator('.pill-selector .pill-selector__pill', { hasText: exactText('ID'), }) .click() @@ -358,11 +358,11 @@ describe('List View', () => { await page.locator('.list-controls__toggle-columns').click() // wait until the column toggle UI is visible and fully expanded - await expect(page.locator('.column-selector')).toBeVisible() + await expect(page.locator('.pill-selector')).toBeVisible() await expect(page.locator('table > thead > tr > th:nth-child(2)')).toHaveText('ID') // ensure the ID column is active - const idButton = page.locator('.column-selector .column-selector__column', { + const idButton = page.locator('.pill-selector .pill-selector__pill', { hasText: exactText('ID'), }) @@ -370,7 +370,7 @@ describe('List View', () => { const buttonClasses = await idButton.getAttribute('class') - if (buttonClasses && !buttonClasses.includes('column-selector__column--active')) { + if (buttonClasses && !buttonClasses.includes('pill-selector__pill--selected')) { await idButton.click() await expect(page.locator(tableRowLocator).first().locator('.cell-id')).toBeVisible() } @@ -827,10 +827,10 @@ describe('List View', () => { await page.goto(postsUrl.list) await page.locator('.list-controls__toggle-columns').click() - await expect(page.locator('.column-selector')).toBeVisible() + await expect(page.locator('.pill-selector')).toBeVisible() await expect( - page.locator(`.column-selector .column-selector__column`, { + page.locator(`.pill-selector .pill-selector__pill`, { hasText: exactText('Hidden Field'), }), ).toBeHidden() @@ -840,10 +840,10 @@ describe('List View', () => { await page.goto(postsUrl.list) await page.locator('.list-controls__toggle-columns').click() - await expect(page.locator('.column-selector')).toBeVisible() + await expect(page.locator('.pill-selector')).toBeVisible() await expect( - page.locator(`.column-selector .column-selector__column`, { + page.locator(`.pill-selector .pill-selector__pill`, { hasText: exactText('Admin Hidden Field'), }), ).toBeVisible() @@ -853,11 +853,11 @@ describe('List View', () => { await page.goto(postsUrl.list) await page.locator('.list-controls__toggle-columns').click() - await expect(page.locator('.column-selector')).toBeVisible() + await expect(page.locator('.pill-selector')).toBeVisible() // Check if "Disable List Column Text" is not present in the column options await expect( - page.locator(`.column-selector .column-selector__column`, { + page.locator(`.pill-selector .pill-selector__pill`, { hasText: exactText('Disable List Column Text'), }), ).toBeHidden() @@ -867,11 +867,11 @@ describe('List View', () => { await page.goto(postsUrl.list) await page.locator('.list-controls__toggle-columns').click() - await expect(page.locator('.column-selector')).toBeVisible() + await expect(page.locator('.pill-selector')).toBeVisible() // Check if "Disable List Filter Text" is present in the column options await expect( - page.locator(`.column-selector .column-selector__column`, { + page.locator(`.pill-selector .pill-selector__pill`, { hasText: exactText('Disable List Filter Text'), }), ).toBeVisible() @@ -895,7 +895,7 @@ describe('List View', () => { await openListColumns(page, {}) const numberOfColumns = await page.locator(tableHeaders).count() - await expect(page.locator('.column-selector')).toBeVisible() + await expect(page.locator('.pill-selector')).toBeVisible() await expect(page.locator('table > thead > tr > th:nth-child(2)')).toHaveText('ID') await toggleColumn(page, { columnLabel: 'ID', columnName: 'id', targetState: 'off' }) @@ -960,7 +960,7 @@ describe('List View', () => { await page.goto(postsUrl.list) await openColumnControls(page) await page - .locator('.column-selector .column-selector__column', { + .locator('.pill-selector .pill-selector__pill', { hasText: exactText('Named Group > Some Text Field'), }) .click() @@ -974,7 +974,7 @@ describe('List View', () => { await page.goto(postsUrl.list) await openColumnControls(page) await page - .locator('.column-selector .column-selector__column', { + .locator('.pill-selector .pill-selector__pill', { hasText: exactText('Text Field In Unnamed Group'), }) .click() @@ -988,7 +988,7 @@ describe('List View', () => { await page.goto(postsUrl.list) await openColumnControls(page) await expect( - page.locator('.column-selector .column-selector__column', { + page.locator('.pill-selector .pill-selector__pill', { hasText: exactText('Named Group'), }), ).toBeHidden() @@ -1003,7 +1003,7 @@ describe('List View', () => { await page.goto(postsUrl.list) await openColumnControls(page) await expect( - page.locator('.column-selector .column-selector__column', { + page.locator('.pill-selector .pill-selector__pill', { hasText: exactText('Group With Custom Cell'), }), ).toBeVisible() @@ -1020,14 +1020,14 @@ describe('List View', () => { // Enable top-level column await page - .locator('.column-selector .column-selector__column', { + .locator('.pill-selector .pill-selector__pill', { hasText: exactText('Some Text Field'), }) .click() // Enable group column await page - .locator('.column-selector .column-selector__column', { + .locator('.pill-selector .pill-selector__pill', { hasText: exactText('Named Group > Some Text Field'), }) .click() @@ -1046,7 +1046,7 @@ describe('List View', () => { await page.goto(postsUrl.list) await openColumnControls(page) await page - .locator('.column-selector .column-selector__column', { + .locator('.pill-selector .pill-selector__pill', { hasText: exactText('Named Tab > Nested Text Field In Named Tab'), }) .click() @@ -1060,7 +1060,7 @@ describe('List View', () => { await page.goto(postsUrl.list) await openColumnControls(page) await page - .locator('.column-selector .column-selector__column', { + .locator('.pill-selector .pill-selector__pill', { hasText: exactText('Nested Text Field In Unnamed Tab'), }) .click() @@ -1076,7 +1076,7 @@ describe('List View', () => { await page.reload() await expect( - page.locator('.list-controls .column-selector .column-selector__column').first(), + page.locator('.list-controls .pill-selector .pill-selector__pill').first(), ).toHaveText('Number') await expect(page.locator('table thead tr th').nth(1)).toHaveText('Number') @@ -1113,7 +1113,7 @@ describe('List View', () => { // ensure that the columns are in the correct order await expect( page - .locator('[id^=list-drawer_1_] .list-controls .column-selector .column-selector__column') + .locator('[id^=list-drawer_1_] .list-controls .pill-selector .pill-selector__pill') .first(), ).toHaveText('Number') }) @@ -1150,11 +1150,11 @@ describe('List View', () => { const columnContainer = page.locator('.list-controls__columns').first() - const column = columnContainer.locator(`.column-selector .column-selector__column`, { + const column = columnContainer.locator(`.pill-selector .pill-selector__pill`, { hasText: exactText('ID'), }) - await expect(column).not.toHaveClass(/column-selector__column--active/) + await expect(column).not.toHaveClass(/pill-selector__pill--selected/) }) test('should retain preferences when changing drawer collections', async () => { @@ -1215,9 +1215,9 @@ describe('List View', () => { // ensure that the "id" column is still deselected await expect( page - .locator('[id^=list-drawer_1_] .list-controls .column-selector .column-selector__column') + .locator('[id^=list-drawer_1_] .list-controls .pill-selector .pill-selector__pill') .first(), - ).not.toHaveClass('column-selector__column--active') + ).not.toHaveClass('pill-selector__pill--selected') // select the "Post" collection again await collectionSelector.click() @@ -1231,9 +1231,9 @@ describe('List View', () => { // ensure that the "number" column is still deselected await expect( page - .locator('[id^=list-drawer_1_] .list-controls .column-selector .column-selector__column') + .locator('[id^=list-drawer_1_] .list-controls .pill-selector .pill-selector__pill') .first(), - ).not.toHaveClass('column-selector__column--active') + ).not.toHaveClass('pill-selector__pill--selected') }) test('should render custom table cell component', async () => { @@ -1418,7 +1418,7 @@ describe('List View', () => { await page.goto(postsUrl.list) await openColumnControls(page) await page - .locator('.column-selector .column-selector__column', { + .locator('.pill-selector .pill-selector__pill', { hasText: exactText('Named Group > Some Text Field'), }) .click() @@ -1450,7 +1450,7 @@ describe('List View', () => { await page.goto(postsUrl.list) await openColumnControls(page) await page - .locator('.column-selector .column-selector__column', { + .locator('.pill-selector .pill-selector__pill', { hasText: exactText('Named Tab > Nested Text Field In Named Tab'), }) .click() @@ -1492,13 +1492,13 @@ describe('List View', () => { await page.waitForURL(/sort=title/) const columnAfterSort = page.locator( - `.list-controls__columns .column-selector .column-selector__column`, + `.list-controls__columns .pill-selector .pill-selector__pill`, { hasText: exactText('ID'), }, ) - await expect(columnAfterSort).not.toHaveClass('column-selector__column--active') + await expect(columnAfterSort).not.toHaveClass('pill-selector__pill--selected') await expect(page.locator('#heading-id')).toBeHidden() await expect(page.locator('.cell-id')).toHaveCount(0) }) @@ -1522,13 +1522,13 @@ describe('List View', () => { await page.locator('#heading-_status').waitFor({ state: 'visible' }) const columnAfterSort = page.locator( - `.list-controls__columns .column-selector .column-selector__column`, + `.list-controls__columns .pill-selector .pill-selector__pill`, { hasText: exactText('Status'), }, ) - await expect(columnAfterSort).toHaveClass(/column-selector__column--active/) + await expect(columnAfterSort).toHaveClass(/pill-selector__pill--selected/) await expect(page.locator('#heading-_status')).toBeVisible() await expect(page.locator('.cell-_status').first()).toBeVisible() @@ -1566,13 +1566,13 @@ describe('List View', () => { // ensure the column is still visible const columnAfterSecondSort = page.locator( - `.list-controls__columns .column-selector .column-selector__column`, + `.list-controls__columns .pill-selector .pill-selector__pill`, { hasText: exactText('Status'), }, ) - await expect(columnAfterSecondSort).toHaveClass(/column-selector__column--active/) + await expect(columnAfterSecondSort).toHaveClass(/pill-selector__pill--selected/) await expect(page.locator('#heading-_status')).toBeVisible() await expect(page.locator('.cell-_status').first()).toBeVisible() }) @@ -1591,7 +1591,7 @@ describe('List View', () => { await page.locator('.list-controls__toggle-columns').click() await expect( - page.locator('.column-selector__column', { + page.locator('.pill-selector__pill', { hasText: exactText('Title'), }), ).toHaveText('Title') @@ -1620,7 +1620,7 @@ describe('List View', () => { await page.locator('.list-controls__toggle-columns').click() // expecting the label to fall back to english as default fallbackLng await expect( - page.locator('.column-selector__column', { + page.locator('.pill-selector__pill', { hasText: exactText('Title'), }), ).toHaveText('Title') diff --git a/test/database/up-down-migration/migrations/20250611_163948.ts b/test/database/up-down-migration/migrations/20250611_163948.ts index 086047bb6..5409567ee 100644 --- a/test/database/up-down-migration/migrations/20250611_163948.ts +++ b/test/database/up-down-migration/migrations/20250611_163948.ts @@ -1,4 +1,4 @@ -import type { MigrateDownArgs, MigrateUpArgs} from '@payloadcms/db-postgres'; +import type { MigrateDownArgs, MigrateUpArgs } from '@payloadcms/db-postgres' import { sql } from '@payloadcms/db-postgres' diff --git a/test/fields/collections/Text/e2e.spec.ts b/test/fields/collections/Text/e2e.spec.ts index 2775f9e64..f8ca7926d 100644 --- a/test/fields/collections/Text/e2e.spec.ts +++ b/test/fields/collections/Text/e2e.spec.ts @@ -84,7 +84,7 @@ describe('Text', () => { const { columnContainer } = await openListColumns(page, {}) await expect( - columnContainer.locator('.column-selector__column', { + columnContainer.locator('.pill-selector__pill', { hasText: exactText('Hidden Text Field'), }), ).toBeHidden() @@ -110,7 +110,7 @@ describe('Text', () => { const { columnContainer } = await openListColumns(page, {}) await expect( - columnContainer.locator('.column-selector__column', { + columnContainer.locator('.pill-selector__pill', { hasText: exactText('Disabled Text Field'), }), ).toBeHidden() @@ -138,7 +138,7 @@ describe('Text', () => { const { columnContainer } = await openListColumns(page, {}) await expect( - columnContainer.locator('.column-selector__column', { + columnContainer.locator('.pill-selector__pill', { hasText: exactText('Admin Hidden Text Field'), }), ).toBeVisible() @@ -184,7 +184,7 @@ describe('Text', () => { await page.goto(url.list) await openListColumns(page, {}) await expect( - page.locator(`.column-selector .column-selector__column`, { + page.locator(`.pill-selector .pill-selector__pill`, { hasText: exactText('Disable List Column Text'), }), ).toBeHidden() @@ -200,7 +200,7 @@ describe('Text', () => { await toggleColumn(page, { targetState: 'on', columnLabel: 'Text en', - columnName: 'localizedText', + columnName: 'i18nText', }) const textCell = page.locator('.row-1 .cell-i18nText') diff --git a/test/helpers/e2e/reorderColumns.ts b/test/helpers/e2e/reorderColumns.ts index 2f2148218..d3b3931a4 100644 --- a/test/helpers/e2e/reorderColumns.ts +++ b/test/helpers/e2e/reorderColumns.ts @@ -29,13 +29,13 @@ export const reorderColumns = async ( await expect(page.locator(`${columnContainerSelector}.rah-static--height-auto`)).toBeVisible() const fromBoundingBox = await columnContainer - .locator(`.column-selector .column-selector__column`, { + .locator(`.pill-selector .pill-selector__pill`, { hasText: exactText(fromColumn), }) .boundingBox() const toBoundingBox = await columnContainer - .locator(`.column-selector .column-selector__column`, { + .locator(`.pill-selector .pill-selector__pill`, { hasText: exactText(toColumn), }) .boundingBox() @@ -51,9 +51,9 @@ export const reorderColumns = async ( await page.mouse.move(toBoundingBox.x - 2, toBoundingBox.y - 2, { steps: 10 }) await page.mouse.up() - await expect( - columnContainer.locator('.column-selector .column-selector__column').first(), - ).toHaveText(fromColumn) + await expect(columnContainer.locator('.pill-selector .pill-selector__pill').first()).toHaveText( + fromColumn, + ) await expect(page.locator('table thead tr th').nth(1).first()).toHaveText(fromColumn) // TODO: This wait makes sure the preferences are actually saved. Just waiting for the UI to update is not enough. We should replace this wait diff --git a/test/helpers/e2e/toggleColumn.ts b/test/helpers/e2e/toggleColumn.ts index eb57bf242..7250d1daa 100644 --- a/test/helpers/e2e/toggleColumn.ts +++ b/test/helpers/e2e/toggleColumn.ts @@ -30,12 +30,12 @@ export const toggleColumn = async ( columnContainerSelector, }) - const column = columnContainer.locator(`.column-selector .column-selector__column`, { + const column = columnContainer.locator(`.pill-selector .pill-selector__pill`, { hasText: exactText(columnLabel), }) const isActiveBeforeClick = await column.evaluate((el) => - el.classList.contains('column-selector__column--active'), + el.classList.contains('pill-selector__pill--selected'), ) const targetState = @@ -52,10 +52,10 @@ export const toggleColumn = async ( if (targetState === 'off') { // no class - await expect(column).not.toHaveClass(/column-selector__column--active/) + await expect(column).not.toHaveClass(/pill-selector__pill--selected/) } else { // has class - await expect(column).toHaveClass(/column-selector__column--active/) + await expect(column).toHaveClass(/pill-selector__pill--selected/) } if (expectURLChange && columnName && requiresToggle) { diff --git a/test/helpers/reInitEndpoint.ts b/test/helpers/reInitEndpoint.ts index 6cac26caa..a6e98cc6e 100644 --- a/test/helpers/reInitEndpoint.ts +++ b/test/helpers/reInitEndpoint.ts @@ -24,7 +24,6 @@ const handler: PayloadHandler = async (req) => { }) try { - console.log('Calling seedDB') await seedDB({ _payload: payload, collectionSlugs: payload.config.collections.map(({ slug }) => slug), diff --git a/test/joins/e2e.spec.ts b/test/joins/e2e.spec.ts index d24ee2223..3928426a3 100644 --- a/test/joins/e2e.spec.ts +++ b/test/joins/e2e.spec.ts @@ -1,7 +1,6 @@ import type { Page } from '@playwright/test' import { expect, test } from '@playwright/test' -import { reorderColumns } from 'helpers/e2e/reorderColumns.js' import * as path from 'path' import { fileURLToPath } from 'url' @@ -18,6 +17,7 @@ import { } from '../helpers.js' import { AdminUrlUtil } from '../helpers/adminUrlUtil.js' import { navigateToDoc } from '../helpers/e2e/navigateToDoc.js' +import { reorderColumns } from '../helpers/e2e/reorderColumns.js' import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js' import { reInitializeDB } from '../helpers/reInitializeDB.js' import { RESTClient } from '../helpers/rest.js' diff --git a/test/joins/payload-types.ts b/test/joins/payload-types.ts index 5f290e335..8f467a089 100644 --- a/test/joins/payload-types.ts +++ b/test/joins/payload-types.ts @@ -91,6 +91,9 @@ export interface Config { folders: Folder; 'example-pages': ExamplePage; 'example-posts': ExamplePost; + folderPoly1: FolderPoly1; + folderPoly2: FolderPoly2; + 'payload-folders': FolderInterface; 'payload-locked-documents': PayloadLockedDocument; 'payload-preferences': PayloadPreference; 'payload-migrations': PayloadMigration; @@ -149,6 +152,9 @@ export interface Config { folders: { children: 'folders' | 'example-pages' | 'example-posts'; }; + 'payload-folders': { + documentsAndFolders: 'payload-folders' | 'folderPoly1' | 'folderPoly2'; + }; }; collectionsSelect: { users: UsersSelect | UsersSelect; @@ -175,6 +181,9 @@ export interface Config { folders: FoldersSelect | FoldersSelect; 'example-pages': ExamplePagesSelect | ExamplePagesSelect; 'example-posts': ExamplePostsSelect | ExamplePostsSelect; + folderPoly1: FolderPoly1Select | FolderPoly1Select; + folderPoly2: FolderPoly2Select | FolderPoly2Select; + 'payload-folders': PayloadFoldersSelect | PayloadFoldersSelect; 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; 'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect; 'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect; @@ -739,6 +748,57 @@ export interface ExamplePost { updatedAt: string; createdAt: string; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "folderPoly1". + */ +export interface FolderPoly1 { + id: string; + folderPoly1Title?: string | null; + folder?: (string | null) | FolderInterface; + updatedAt: string; + createdAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-folders". + */ +export interface FolderInterface { + id: string; + name: string; + folder?: (string | null) | FolderInterface; + documentsAndFolders?: { + docs?: ( + | { + relationTo?: 'payload-folders'; + value: string | FolderInterface; + } + | { + relationTo?: 'folderPoly1'; + value: string | FolderPoly1; + } + | { + relationTo?: 'folderPoly2'; + value: string | FolderPoly2; + } + )[]; + hasNextPage?: boolean; + totalDocs?: number; + }; + updatedAt: string; + createdAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "folderPoly2". + */ +export interface FolderPoly2 { + id: string; + folderPoly2Title?: string | null; + folder?: (string | null) | FolderInterface; + updatedAt: string; + createdAt: string; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-locked-documents". @@ -841,6 +901,18 @@ export interface PayloadLockedDocument { | ({ relationTo: 'example-posts'; value: string | ExamplePost; + } | null) + | ({ + relationTo: 'folderPoly1'; + value: string | FolderPoly1; + } | null) + | ({ + relationTo: 'folderPoly2'; + value: string | FolderPoly2; + } | null) + | ({ + relationTo: 'payload-folders'; + value: string | FolderInterface; } | null); globalSlug?: string | null; user: { @@ -1216,6 +1288,37 @@ export interface ExamplePostsSelect { updatedAt?: T; createdAt?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "folderPoly1_select". + */ +export interface FolderPoly1Select { + folderPoly1Title?: T; + folder?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "folderPoly2_select". + */ +export interface FolderPoly2Select { + folderPoly2Title?: T; + folder?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-folders_select". + */ +export interface PayloadFoldersSelect { + name?: T; + folder?: T; + documentsAndFolders?: T; + updatedAt?: T; + createdAt?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-locked-documents_select". diff --git a/test/localization/e2e.spec.ts b/test/localization/e2e.spec.ts index b1f188430..2994269be 100644 --- a/test/localization/e2e.spec.ts +++ b/test/localization/e2e.spec.ts @@ -122,11 +122,14 @@ describe('Localization', () => { await page.locator('#action-save').click() await page.locator('text=Versions').click() - const firstVersion = findTableRow(page, 'Current Published Version') + const firstVersion = findTableRow(page, 'Currently Published') await firstVersion.locator('a').click() - await expect(page.locator('.select-version-locales__label')).toBeVisible() - await expect(page.locator('.select-version-locales .react-select')).not.toContainText( + await expect(page.locator('.view-version__toggle-locales')).toBeVisible() + await page.locator('.view-version__toggle-locales').click() + + await expect(page.locator('.select-version-locales .pill-selector')).toBeVisible() + await expect(page.locator('.select-version-locales .pill-selector')).not.toContainText( 'FILTERED', ) }) diff --git a/test/query-presets/e2e.spec.ts b/test/query-presets/e2e.spec.ts index 66af7f873..14ded55fc 100644 --- a/test/query-presets/e2e.spec.ts +++ b/test/query-presets/e2e.spec.ts @@ -330,7 +330,7 @@ describe('Query Presets', () => { const { columnContainer } = await toggleColumn(page, { columnLabel: 'ID' }) - const column = columnContainer.locator(`.column-selector .column-selector__column`, { + const column = columnContainer.locator(`.pill-selector .pill-selector__pill`, { hasText: exactText('ID'), }) @@ -338,7 +338,7 @@ describe('Query Presets', () => { await clickListMenuItem({ page, menuItemLabel: 'Reset' }) await openListColumns(page, {}) - await expect(column).toHaveClass(/column-selector__column--active/) + await expect(column).toHaveClass(/pill-selector__pill--selected/) }) test('should only enter modified state when changes are made to an active preset', async () => { diff --git a/test/uploads/e2e.spec.ts b/test/uploads/e2e.spec.ts index 5aaeac935..d61fcf6c8 100644 --- a/test/uploads/e2e.spec.ts +++ b/test/uploads/e2e.spec.ts @@ -1369,14 +1369,14 @@ describe('Uploads', () => { // Show all columns with relations await page.locator('.list-controls__toggle-columns').click() - await expect(page.locator('.column-selector')).toBeVisible() - const imageWithoutPreview2Button = page.locator(`.column-selector .column-selector__column`, { + await expect(page.locator('.pill-selector')).toBeVisible() + const imageWithoutPreview2Button = page.locator(`.pill-selector .pill-selector__pill`, { hasText: exactText('Image Without Preview2'), }) - const imageWithPreview3Button = page.locator(`.column-selector .column-selector__column`, { + const imageWithPreview3Button = page.locator(`.pill-selector .pill-selector__pill`, { hasText: exactText('Image With Preview3'), }) - const imageWithoutPreview3Button = page.locator(`.column-selector .column-selector__column`, { + const imageWithoutPreview3Button = page.locator(`.pill-selector .pill-selector__pill`, { hasText: exactText('Image Without Preview3'), }) await imageWithoutPreview2Button.click() diff --git a/test/versions/.gitignore b/test/versions/.gitignore index 3f549faf9..665b14556 100644 --- a/test/versions/.gitignore +++ b/test/versions/.gitignore @@ -1 +1,2 @@ uploads +uploads2 diff --git a/test/versions/collections/Diff/index.ts b/test/versions/collections/Diff/index.ts index ffbcc0615..f1676e072 100644 --- a/test/versions/collections/Diff/index.ts +++ b/test/versions/collections/Diff/index.ts @@ -143,6 +143,10 @@ export const Diff: CollectionConfig = { type: 'point', name: 'point', }, + { + type: 'json', + name: 'json', + }, { type: 'radio', name: 'radio', @@ -162,6 +166,29 @@ export const Diff: CollectionConfig = { name: 'relationship', relationTo: draftCollectionSlug, }, + { + type: 'relationship', + name: 'relationshipHasMany', + hasMany: true, + relationTo: draftCollectionSlug, + }, + { + type: 'relationship', + name: 'relationshipPolymorphic', + relationTo: [draftCollectionSlug, 'text'], + }, + { + type: 'relationship', + name: 'relationshipHasManyPolymorphic', + hasMany: true, + relationTo: [draftCollectionSlug, 'text'], + }, + { + type: 'relationship', + name: 'relationshipHasManyPolymorphic2', + hasMany: true, + relationTo: [draftCollectionSlug, 'text'], + }, { name: 'richtext', type: 'richText', @@ -234,8 +261,15 @@ export const Diff: CollectionConfig = { relationTo: 'media', type: 'upload', }, + { + name: 'uploadHasMany', + hasMany: true, + relationTo: 'media', + type: 'upload', + }, ], versions: { + drafts: true, maxPerDoc: 35, }, } diff --git a/test/versions/collections/Media2.ts b/test/versions/collections/Media2.ts new file mode 100644 index 000000000..b75051858 --- /dev/null +++ b/test/versions/collections/Media2.ts @@ -0,0 +1,17 @@ +import type { CollectionConfig } from 'payload' + +import path from 'path' +import { fileURLToPath } from 'url' + +import { media2CollectionSlug } from '../slugs.js' + +const filename = fileURLToPath(import.meta.url) +const dirname = path.dirname(filename) + +export const Media2: CollectionConfig = { + fields: [], + slug: media2CollectionSlug, + upload: { + staticDir: path.resolve(dirname, './uploads2'), + }, +} diff --git a/test/versions/config.ts b/test/versions/config.ts index 723d6e145..665abff20 100644 --- a/test/versions/config.ts +++ b/test/versions/config.ts @@ -15,6 +15,7 @@ import DraftsWithValidate from './collections/DraftsWithValidate.js' import ErrorOnUnpublish from './collections/ErrorOnUnpublish.js' import LocalizedPosts from './collections/Localized.js' import { Media } from './collections/Media.js' +import { Media2 } from './collections/Media2.js' import Posts from './collections/Posts.js' import { TextCollection } from './collections/Text.js' import VersionPosts from './collections/Versions.js' @@ -51,6 +52,7 @@ export default buildConfigWithDefaults({ Diff, TextCollection, Media, + Media2, ], globals: [ AutosaveGlobal, diff --git a/test/versions/e2e.spec.ts b/test/versions/e2e.spec.ts index 6eb151724..9978dbff6 100644 --- a/test/versions/e2e.spec.ts +++ b/test/versions/e2e.spec.ts @@ -202,7 +202,7 @@ describe('Versions', () => { const versionID = await row2.locator('.cell-id').textContent() await page.goto(`${savedDocURL}/versions/${versionID}`) await expect(page.locator('.render-field-diffs')).toBeVisible() - await page.locator('.restore-version__button').click() + await page.locator('.restore-version__restore-as-draft-button').click() await page.locator('button:has-text("Confirm")').click() await page.waitForURL(savedDocURL) await expect(page.locator('#field-title')).toHaveValue('v1') @@ -220,7 +220,7 @@ describe('Versions', () => { }) await page.goto(`${url.edit(publishedDoc.id)}/versions`) - await expect(page.locator('main.versions')).toContainText('Current Published Version') + await expect(page.locator('main.versions')).toContainText('Currently Published') }) test('should show unpublished version status in versions view', async () => { @@ -1245,6 +1245,7 @@ describe('Versions', () => { beforeEach(async () => { const newPost = await payload.create({ collection: draftCollectionSlug, + depth: 0, data: { title: 'new post', description: 'new description', @@ -1257,6 +1258,7 @@ describe('Versions', () => { collection: draftCollectionSlug, id: postID, draft: true, + depth: 0, data: { title: 'draft post', description: 'draft description', @@ -1272,6 +1274,8 @@ describe('Versions', () => { const versions = await payload.findVersions({ collection: draftCollectionSlug, + limit: 1, + depth: 0, where: { parent: { equals: postID }, }, @@ -1282,6 +1286,8 @@ describe('Versions', () => { const diffDoc = ( await payload.find({ collection: diffCollectionSlug, + depth: 0, + limit: 1, }) ).docs[0] as Diff @@ -1290,6 +1296,8 @@ describe('Versions', () => { const versionDiff = ( await payload.findVersions({ collection: diffCollectionSlug, + depth: 0, + limit: 1, where: { parent: { equals: diffID }, }, @@ -1299,25 +1307,25 @@ describe('Versions', () => { versionDiffID = versionDiff.id }) - async function navigateToVersionDiff() { + async function navigateToDraftVersionView() { const versionURL = `${serverURL}/admin/collections/${draftCollectionSlug}/${postID}/versions/${versionID}` await page.goto(versionURL) await expect(page.locator('.render-field-diffs').first()).toBeVisible() } - async function navigateToVersionFieldsDiff() { + async function navigateToDiffVersionView() { const versionURL = `${serverURL}/admin/collections/${diffCollectionSlug}/${diffID}/versions/${versionDiffID}` await page.goto(versionURL) await expect(page.locator('.render-field-diffs').first()).toBeVisible() } test('should render diff', async () => { - await navigateToVersionDiff() + await navigateToDraftVersionView() expect(true).toBe(true) }) test('should render diff for nested fields', async () => { - await navigateToVersionDiff() + await navigateToDraftVersionView() const blocksDiffLabel = page.getByText('Blocks Field', { exact: true }) await expect(blocksDiffLabel).toBeVisible() @@ -1336,7 +1344,7 @@ describe('Versions', () => { }) test('should render diff collapser for nested fields', async () => { - await navigateToVersionDiff() + await navigateToDraftVersionView() const blocksDiffLabel = page.getByText('Blocks Field', { exact: true }) await expect(blocksDiffLabel).toBeVisible() @@ -1345,10 +1353,15 @@ describe('Versions', () => { const blocksDiff = page.locator('.iterable-diff', { has: blocksDiffLabel }) await expect(blocksDiff).toBeVisible() + // Collapse to show iterable fields change count + await blocksDiff.locator('.diff-collapser__toggle-button').first().click() + // Expect iterable change count to be visible const iterableChangeCount = blocksDiff.locator('.diff-collapser__field-change-count').first() await expect(iterableChangeCount).toHaveText('2 changed fields') + await blocksDiff.locator('.diff-collapser__toggle-button').first().click() + // Expect iterable rows to be visible const blocksDiffRows = blocksDiff.locator('.iterable-diff__rows') await expect(blocksDiffRows).toBeVisible() @@ -1357,11 +1370,13 @@ describe('Versions', () => { const firstBlocksDiffRow = blocksDiffRows.locator('.iterable-diff__row').first() await expect(firstBlocksDiffRow).toBeVisible() + await firstBlocksDiffRow.locator('.diff-collapser__toggle-button').first().click() // Expect first row change count to be visible const firstBlocksDiffRowChangeCount = firstBlocksDiffRow .locator('.diff-collapser__field-change-count') .first() await expect(firstBlocksDiffRowChangeCount).toHaveText('2 changed fields') + await firstBlocksDiffRow.locator('.diff-collapser__toggle-button').first().click() // Expect collapser content to be visible const diffCollapserContent = blocksDiffRows.locator('.diff-collapser__content') @@ -1385,31 +1400,27 @@ describe('Versions', () => { }) test('correctly renders diff for array fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const textInArray = page.locator('[data-field-path="array.0.textInArray"]') - await expect(textInArray.locator('tr').nth(1).locator('td').nth(1)).toHaveText('textInArray') - await expect(textInArray.locator('tr').nth(1).locator('td').nth(3)).toHaveText('textInArray2') + await expect(textInArray.locator('.html-diff__diff-old')).toHaveText('textInArray') + await expect(textInArray.locator('.html-diff__diff-new')).toHaveText('textInArray2') }) test('correctly renders diff for localized array fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const textInArray = page .locator('[data-field-path="arrayLocalized"][data-locale="en"]') .locator('[data-field-path="arrayLocalized.0.textInArrayLocalized"]') - await expect(textInArray.locator('tr').nth(1).locator('td').nth(1)).toHaveText( - 'textInArrayLocalized', - ) - await expect(textInArray.locator('tr').nth(1).locator('td').nth(3)).toHaveText( - 'textInArrayLocalized2', - ) + await expect(textInArray.locator('.html-diff__diff-old')).toHaveText('textInArrayLocalized') + await expect(textInArray.locator('.html-diff__diff-new')).toHaveText('textInArrayLocalized2') }) test('correctly renders modified-only diff for localized array fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const textInArrayES = page.locator('[data-field-path="arrayLocalized"][data-locale="es"]') @@ -1421,161 +1432,153 @@ describe('Versions', () => { }) test('correctly renders diff for block fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const textInBlock = page.locator('[data-field-path="blocks.0.textInBlock"]') - await expect(textInBlock.locator('tr').nth(1).locator('td').nth(1)).toHaveText('textInBlock') - await expect(textInBlock.locator('tr').nth(1).locator('td').nth(3)).toHaveText('textInBlock2') + await expect(textInBlock.locator('.html-diff__diff-old')).toHaveText('textInBlock') + await expect(textInBlock.locator('.html-diff__diff-new')).toHaveText('textInBlock2') }) test('correctly renders diff for collapsibles within block fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const textInBlock = page.locator( '[data-field-path="blocks.1.textInCollapsibleInCollapsibleBlock"]', ) - await expect(textInBlock.locator('tr').nth(1).locator('td').nth(1)).toHaveText( + await expect(textInBlock.locator('.html-diff__diff-old')).toHaveText( 'textInCollapsibleInCollapsibleBlock', ) - await expect(textInBlock.locator('tr').nth(1).locator('td').nth(3)).toHaveText( + await expect(textInBlock.locator('.html-diff__diff-new')).toHaveText( 'textInCollapsibleInCollapsibleBlock2', ) }) test('correctly renders diff for rows within block fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const textInBlock = page.locator('[data-field-path="blocks.1.textInRowInCollapsibleBlock"]') - await expect(textInBlock.locator('tr').nth(1).locator('td').nth(1)).toHaveText( + await expect(textInBlock.locator('.html-diff__diff-old')).toHaveText( 'textInRowInCollapsibleBlock', ) - await expect(textInBlock.locator('tr').nth(1).locator('td').nth(3)).toHaveText( + await expect(textInBlock.locator('.html-diff__diff-new')).toHaveText( 'textInRowInCollapsibleBlock2', ) }) test('correctly renders diff for named tabs within block fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const textInBlock = page.locator( '[data-field-path="blocks.2.namedTab1InBlock.textInNamedTab1InBlock"]', ) - await expect(textInBlock.locator('tr').nth(1).locator('td').nth(1)).toHaveText( - 'textInNamedTab1InBlock', - ) - await expect(textInBlock.locator('tr').nth(1).locator('td').nth(3)).toHaveText( + await expect(textInBlock.locator('.html-diff__diff-old')).toHaveText('textInNamedTab1InBlock') + await expect(textInBlock.locator('.html-diff__diff-new')).toHaveText( 'textInNamedTab1InBlock2', ) }) test('correctly renders diff for unnamed tabs within block fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const textInBlock = page.locator('[data-field-path="blocks.2.textInUnnamedTab2InBlock"]') - await expect(textInBlock.locator('tr').nth(1).locator('td').nth(1)).toHaveText( + await expect(textInBlock.locator('.html-diff__diff-old')).toHaveText( 'textInUnnamedTab2InBlock', ) - await expect(textInBlock.locator('tr').nth(1).locator('td').nth(3)).toHaveText( + await expect(textInBlock.locator('.html-diff__diff-new')).toHaveText( 'textInUnnamedTab2InBlock2', ) }) test('correctly renders diff for checkbox fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const checkbox = page.locator('[data-field-path="checkbox"]') - await expect(checkbox.locator('tr').nth(1).locator('td').nth(1)).toHaveText('true') - await expect(checkbox.locator('tr').nth(1).locator('td').nth(3)).toHaveText('false') + await expect(checkbox.locator('.html-diff__diff-old')).toHaveText('true') + await expect(checkbox.locator('.html-diff__diff-new')).toHaveText('false') }) test('correctly renders diff for code fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const code = page.locator('[data-field-path="code"]') - await expect(code.locator('tr').nth(1).locator('td').nth(1)).toHaveText('code') - await expect(code.locator('tr').nth(1).locator('td').nth(3)).toHaveText('code2') + await expect(code.locator('.html-diff__diff-old')).toHaveText('code') + await expect(code.locator('.html-diff__diff-new')).toHaveText('code2') }) test('correctly renders diff for collapsible fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const collapsible = page.locator('[data-field-path="textInCollapsible"]') - await expect(collapsible.locator('tr').nth(1).locator('td').nth(1)).toHaveText( - 'textInCollapsible', - ) - await expect(collapsible.locator('tr').nth(1).locator('td').nth(3)).toHaveText( - 'textInCollapsible2', - ) + await expect(collapsible.locator('.html-diff__diff-old')).toHaveText('textInCollapsible') + await expect(collapsible.locator('.html-diff__diff-new')).toHaveText('textInCollapsible2') }) test('correctly renders diff for date fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const date = page.locator('[data-field-path="date"]') - await expect(date.locator('tr').nth(1).locator('td').nth(1)).toHaveText( - '2021-01-01T00:00:00.000Z', - ) - await expect(date.locator('tr').nth(1).locator('td').nth(3)).toHaveText( - '2023-01-01T00:00:00.000Z', - ) + await expect(date.locator('.html-diff__diff-old')).toContainText(' 2021, ') + await expect(date.locator('.html-diff__diff-new')).toContainText(' 2023, ') + // Do not check for exact date, as it may vary based on + // timezone of the test runner which could cause flakiness }) test('correctly renders diff for email fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const email = page.locator('[data-field-path="email"]') - await expect(email.locator('tr').nth(1).locator('td').nth(1)).toHaveText('email@email.com') - await expect(email.locator('tr').nth(1).locator('td').nth(3)).toHaveText('email2@email.com') + await expect(email.locator('.html-diff__diff-old')).toHaveText('email@email.com') + await expect(email.locator('.html-diff__diff-new')).toHaveText('email2@email.com') }) test('correctly renders diff for group fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const group = page.locator('[data-field-path="group.textInGroup"]') - await expect(group.locator('tr').nth(1).locator('td').nth(1)).toHaveText('textInGroup') - await expect(group.locator('tr').nth(1).locator('td').nth(3)).toHaveText('textInGroup2') + await expect(group.locator('.html-diff__diff-old')).toHaveText('textInGroup') + await expect(group.locator('.html-diff__diff-new')).toHaveText('textInGroup2') }) test('correctly renders diff for number fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const number = page.locator('[data-field-path="number"]') - await expect(number.locator('tr').nth(1).locator('td').nth(1)).toHaveText('1') - await expect(number.locator('tr').nth(1).locator('td').nth(3)).toHaveText('2') + await expect(number.locator('.html-diff__diff-old')).toHaveText('1') + await expect(number.locator('.html-diff__diff-new')).toHaveText('2') }) test('correctly renders diff for point fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const point = page.locator('[data-field-path="point"]') - await expect(point.locator('tr').nth(3).locator('td').nth(1)).toHaveText('2') - await expect(point.locator('tr').nth(3).locator('td').nth(3)).toHaveText('3') + await expect(point.locator('.html-diff__diff-old')).toHaveText('[\n 1,\n 2\n]') + await expect(point.locator('.html-diff__diff-new')).toHaveText('[\n 1,\n 3\n]') }) test('correctly renders diff for radio fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const radio = page.locator('[data-field-path="radio"]') - await expect(radio.locator('tr').nth(1).locator('td').nth(1)).toHaveText('Option 1') - await expect(radio.locator('tr').nth(1).locator('td').nth(3)).toHaveText('Option 2') + await expect(radio.locator('.html-diff__diff-old')).toHaveText('Option 1') + await expect(radio.locator('.html-diff__diff-new')).toHaveText('Option 2') }) test('correctly renders diff for relationship fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const relationship = page.locator('[data-field-path="relationship"]') @@ -1583,23 +1586,24 @@ describe('Versions', () => { collection: 'draft-posts', sort: 'createdAt', limit: 3, + depth: 0, }) - await expect(relationship.locator('tr').nth(1).locator('td').nth(1)).toHaveText( - String(draftDocs?.docs?.[1]?.id), - ) - await expect(relationship.locator('tr').nth(1).locator('td').nth(3)).toHaveText( - String(draftDocs?.docs?.[2]?.id), - ) + await expect( + relationship.locator('.html-diff__diff-old .relationship-diff__info'), + ).toHaveText(String(draftDocs?.docs?.[1]?.title)) + await expect( + relationship.locator('.html-diff__diff-new .relationship-diff__info'), + ).toHaveText(String(draftDocs?.docs?.[2]?.title)) }) test('correctly renders diff for richtext fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const richtext = page.locator('[data-field-path="richtext"]') - const oldDiff = richtext.locator('.lexical-diff__diff-old') - const newDiff = richtext.locator('.lexical-diff__diff-new') + const oldDiff = richtext.locator('.html-diff__diff-old') + const newDiff = richtext.locator('.html-diff__diff-new') const oldHTML = `Fugiat essein dolor aleiqua cillum proident ad cillum excepteur mollit reprehenderit mollit commodo. Pariatur incididunt non exercitation est mollit nisi laboredeleteofficia cupidatat amet commodo commodo proident occaecat. @@ -1612,7 +1616,7 @@ describe('Versions', () => { }) test('correctly renders diff for richtext fields with custom Diff component', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const richtextWithCustomDiff = page.locator('[data-field-path="richtextWithCustomDiff"]') @@ -1620,83 +1624,78 @@ describe('Versions', () => { }) test('correctly renders diff for row fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const textInRow = page.locator('[data-field-path="textInRow"]') - await expect(textInRow.locator('tr').nth(1).locator('td').nth(1)).toHaveText('textInRow') - await expect(textInRow.locator('tr').nth(1).locator('td').nth(3)).toHaveText('textInRow2') + await expect(textInRow.locator('.html-diff__diff-old')).toHaveText('textInRow') + await expect(textInRow.locator('.html-diff__diff-new')).toHaveText('textInRow2') }) test('correctly renders diff for select fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const select = page.locator('[data-field-path="select"]') - await expect(select.locator('tr').nth(1).locator('td').nth(1)).toHaveText('Option 1') - await expect(select.locator('tr').nth(1).locator('td').nth(3)).toHaveText('Option 2') + await expect(select.locator('.html-diff__diff-old')).toHaveText('Option 1') + await expect(select.locator('.html-diff__diff-new')).toHaveText('Option 2') }) test('correctly renders diff for named tabs', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const textInNamedTab1 = page.locator('[data-field-path="namedTab1.textInNamedTab1"]') - await expect(textInNamedTab1.locator('tr').nth(1).locator('td').nth(1)).toHaveText( - 'textInNamedTab1', - ) - await expect(textInNamedTab1.locator('tr').nth(1).locator('td').nth(3)).toHaveText( - 'textInNamedTab12', - ) + await expect(textInNamedTab1.locator('.html-diff__diff-old')).toHaveText('textInNamedTab1') + await expect(textInNamedTab1.locator('.html-diff__diff-new')).toHaveText('textInNamedTab12') }) test('correctly renders diff for unnamed tabs', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const textInUnamedTab2 = page.locator('[data-field-path="textInUnnamedTab2"]') - await expect(textInUnamedTab2.locator('tr').nth(1).locator('td').nth(1)).toHaveText( - 'textInUnnamedTab2', - ) - await expect(textInUnamedTab2.locator('tr').nth(1).locator('td').nth(3)).toHaveText( + await expect(textInUnamedTab2.locator('.html-diff__diff-old')).toHaveText('textInUnnamedTab2') + await expect(textInUnamedTab2.locator('.html-diff__diff-new')).toHaveText( 'textInUnnamedTab22', ) }) test('correctly renders diff for text fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const text = page.locator('[data-field-path="text"]') - await expect(text.locator('tr').nth(1).locator('td').nth(1)).toHaveText('text') - await expect(text.locator('tr').nth(1).locator('td').nth(3)).toHaveText('text2') + await expect(text.locator('.html-diff__diff-old')).toHaveText('text') + await expect(text.locator('.html-diff__diff-new')).toHaveText('text2') }) test('correctly renders diff for textArea fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const textArea = page.locator('[data-field-path="textArea"]') - await expect(textArea.locator('tr').nth(1).locator('td').nth(1)).toHaveText('textArea') - await expect(textArea.locator('tr').nth(1).locator('td').nth(3)).toHaveText('textArea2') + await expect(textArea.locator('.html-diff__diff-old')).toHaveText('textArea') + await expect(textArea.locator('.html-diff__diff-new')).toHaveText('textArea2') }) test('correctly renders diff for upload fields', async () => { - await navigateToVersionFieldsDiff() + await navigateToDiffVersionView() const upload = page.locator('[data-field-path="upload"]') const uploadDocs = await payload.find({ collection: 'media', sort: 'createdAt', + depth: 0, limit: 2, }) - await expect(upload.locator('tr').nth(1).locator('td').nth(1)).toHaveText( - String(uploadDocs?.docs?.[0]?.id), + await expect(upload.locator('.html-diff__diff-old .upload-diff__info')).toHaveText( + String(uploadDocs?.docs?.[0]?.filename), ) - await expect(upload.locator('tr').nth(1).locator('td').nth(3)).toHaveText( - String(uploadDocs?.docs?.[1]?.id), + await expect(upload.locator('.html-diff__diff-new .upload-diff__info')).toHaveText( + String(uploadDocs?.docs?.[1]?.filename), ) }) }) diff --git a/test/versions/payload-types.ts b/test/versions/payload-types.ts index f71cbfeae..d5b3d5fc3 100644 --- a/test/versions/payload-types.ts +++ b/test/versions/payload-types.ts @@ -82,6 +82,7 @@ export interface Config { diff: Diff; text: Text; media: Media; + media2: Media2; users: User; 'payload-jobs': PayloadJob; 'payload-locked-documents': PayloadLockedDocument; @@ -105,6 +106,7 @@ export interface Config { diff: DiffSelect | DiffSelect; text: TextSelect | TextSelect; media: MediaSelect | MediaSelect; + media2: Media2Select | Media2Select; users: UsersSelect | UsersSelect; 'payload-jobs': PayloadJobsSelect | PayloadJobsSelect; 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; @@ -384,8 +386,51 @@ export interface Diff { * @maxItems 2 */ point?: [number, number] | null; + json?: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; radio?: ('option1' | 'option2') | null; relationship?: (string | null) | DraftPost; + relationshipHasMany?: (string | DraftPost)[] | null; + relationshipPolymorphic?: + | ({ + relationTo: 'draft-posts'; + value: string | DraftPost; + } | null) + | ({ + relationTo: 'text'; + value: string | Text; + } | null); + relationshipHasManyPolymorphic?: + | ( + | { + relationTo: 'draft-posts'; + value: string | DraftPost; + } + | { + relationTo: 'text'; + value: string | Text; + } + )[] + | null; + relationshipHasManyPolymorphic2?: + | ( + | { + relationTo: 'draft-posts'; + value: string | DraftPost; + } + | { + relationTo: 'text'; + value: string | Text; + } + )[] + | null; richtext?: { root: { type: string; @@ -425,6 +470,18 @@ export interface Diff { text?: string | null; textArea?: string | null; upload?: (string | null) | Media; + uploadHasMany?: (string | Media)[] | null; + updatedAt: string; + createdAt: string; + _status?: ('draft' | 'published') | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "text". + */ +export interface Text { + id: string; + text: string; updatedAt: string; createdAt: string; } @@ -448,13 +505,21 @@ export interface Media { } /** * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "text". + * via the `definition` "media2". */ -export interface Text { +export interface Media2 { id: string; - text: string; updatedAt: string; createdAt: string; + url?: string | null; + thumbnailURL?: string | null; + filename?: string | null; + mimeType?: string | null; + filesize?: number | null; + width?: number | null; + height?: number | null; + focalX?: number | null; + focalY?: number | null; } /** * This interface was referenced by `Config`'s JSON-Schema @@ -632,6 +697,10 @@ export interface PayloadLockedDocument { relationTo: 'media'; value: string | Media; } | null) + | ({ + relationTo: 'media2'; + value: string | Media2; + } | null) | ({ relationTo: 'users'; value: string | User; @@ -898,8 +967,13 @@ export interface DiffSelect { }; number?: T; point?: T; + json?: T; radio?: T; relationship?: T; + relationshipHasMany?: T; + relationshipPolymorphic?: T; + relationshipHasManyPolymorphic?: T; + relationshipHasManyPolymorphic2?: T; richtext?: T; richtextWithCustomDiff?: T; textInRow?: T; @@ -913,8 +987,10 @@ export interface DiffSelect { text?: T; textArea?: T; upload?: T; + uploadHasMany?: T; updatedAt?: T; createdAt?: T; + _status?: T; } /** * This interface was referenced by `Config`'s JSON-Schema @@ -942,6 +1018,23 @@ export interface MediaSelect { focalX?: T; focalY?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "media2_select". + */ +export interface Media2Select { + updatedAt?: T; + createdAt?: T; + url?: T; + thumbnailURL?: T; + filename?: T; + mimeType?: T; + filesize?: T; + width?: T; + height?: T; + focalX?: T; + focalY?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "users_select". diff --git a/test/versions/seed.ts b/test/versions/seed.ts index d72549545..ed7ef8aa6 100644 --- a/test/versions/seed.ts +++ b/test/versions/seed.ts @@ -11,6 +11,7 @@ import { autosaveWithValidateCollectionSlug, diffCollectionSlug, draftCollectionSlug, + media2CollectionSlug, mediaCollectionSlug, } from './slugs.js' import { textToLexicalJSON } from './textToLexicalJSON.js' @@ -36,6 +37,12 @@ export async function seed(_payload: Payload, parallel: boolean = false) { file: imageFile, }) + const { id: uploadedImageMedia2 } = await _payload.create({ + collection: media2CollectionSlug, + data: {}, + file: imageFile, + }) + const imageFilePath2 = path.resolve(dirname, './image.png') const imageFile2 = await getFileByPath(imageFilePath2) @@ -45,6 +52,12 @@ export async function seed(_payload: Payload, parallel: boolean = false) { file: imageFile2, }) + const { id: uploadedImage2Media2 } = await _payload.create({ + collection: media2CollectionSlug, + data: {}, + file: imageFile2, + }) + await executePromises( [ () => @@ -113,6 +126,20 @@ export async function seed(_payload: Payload, parallel: boolean = false) { draft: false, }) + const draft3 = await _payload.create({ + collection: draftCollectionSlug, + data: { + _status: 'published', + blocksField, + description: 'Description2', + radio: 'test', + title: 'Another Published Title', + }, + depth: 0, + overrideAccess: true, + draft: false, + }) + await _payload.create({ collection: autosaveWithValidateCollectionSlug, data: { @@ -134,10 +161,54 @@ export async function seed(_payload: Payload, parallel: boolean = false) { }, }) - const diffDoc = await _payload.create({ + const diffDocDraft = await _payload.create({ collection: diffCollectionSlug, locale: 'en', data: { + _status: 'draft', + text: 'Draft 1', + }, + depth: 0, + }) + + await _payload.update({ + collection: diffCollectionSlug, + locale: 'en', + data: { + _status: 'draft', + text: 'Draft 2', + }, + depth: 0, + id: diffDocDraft.id, + }) + + await _payload.update({ + collection: diffCollectionSlug, + locale: 'en', + data: { + _status: 'draft', + text: 'Draft 3', + }, + depth: 0, + id: diffDocDraft.id, + }) + await _payload.update({ + collection: diffCollectionSlug, + locale: 'en', + data: { + _status: 'draft', + text: 'Draft 4', + }, + depth: 0, + id: diffDocDraft.id, + }) + + const diffDoc = await _payload.update({ + collection: diffCollectionSlug, + locale: 'en', + id: diffDocDraft.id, + data: { + _status: 'published', array: [ { textInArray: 'textInArray', @@ -168,7 +239,7 @@ export async function seed(_payload: Payload, parallel: boolean = false) { ], checkbox: true, code: 'code', - date: '2021-01-01T00:00:00.000Z', + date: '2021-04-01T00:00:00.000Z', email: 'email@email.com', group: { textInGroup: 'textInGroup', @@ -178,8 +249,19 @@ export async function seed(_payload: Payload, parallel: boolean = false) { }, number: 1, point: [1, 2], + json: { + text: 'json', + number: 1, + boolean: true, + array: [ + { + textInArrayInJson: 'textInArrayInJson', + }, + ], + }, radio: 'option1', relationship: manyDraftsID, + relationshipHasMany: [manyDraftsID], richtext: generateLexicalData({ mediaID: uploadedImage, textID: doc1ID, @@ -192,16 +274,73 @@ export async function seed(_payload: Payload, parallel: boolean = false) { textInCollapsible: 'textInCollapsible', textInRow: 'textInRow', textInUnnamedTab2: 'textInUnnamedTab2', + relationshipPolymorphic: { + relationTo: 'text', + value: doc1ID, + }, + relationshipHasManyPolymorphic: [ + { + relationTo: 'text', + value: doc1ID, + }, + ], + relationshipHasManyPolymorphic2: [ + { + relationTo: 'text', + value: doc1ID, + }, + { + relationTo: draftCollectionSlug, + value: draft2.id, + }, + ], upload: uploadedImage, + uploadHasMany: [uploadedImage], }, depth: 0, }) + await _payload.db.updateOne({ + collection: diffCollectionSlug, + id: diffDoc.id, + data: { + ...diffDoc, + createdAt: new Date(new Date(diffDoc.createdAt).getTime() - 2 * 60 * 10000).toISOString(), + updatedAt: new Date(new Date(diffDoc.updatedAt).getTime() - 2 * 60 * 10000).toISOString(), + }, + }) + + const versions = await _payload.findVersions({ + collection: diffCollectionSlug, + depth: 0, + limit: 50, + sort: '-createdAt', + }) + + let i = 0 + for (const version of versions.docs) { + i += 1 + await _payload.db.updateVersion({ + id: version.id, + collection: diffCollectionSlug, + versionData: { + ...version.version, + createdAt: new Date( + new Date(version.createdAt).getTime() - 2 * 60 * 10000 * i, + ).toISOString(), + updatedAt: new Date( + new Date(version.updatedAt).getTime() - 2 * 60 * 10000 * i, + ).toISOString(), + }, + }) + } + const updatedDiffDoc = await _payload.update({ id: diffDoc.id, collection: diffCollectionSlug, locale: 'en', data: { + _status: 'published', array: [ { textInArray: 'textInArray2', @@ -232,7 +371,7 @@ export async function seed(_payload: Payload, parallel: boolean = false) { ], checkbox: false, code: 'code2', - date: '2023-01-01T00:00:00.000Z', + date: '2023-04-01T00:00:00.000Z', email: 'email2@email.com', group: { textInGroup: 'textInGroup2', @@ -241,9 +380,44 @@ export async function seed(_payload: Payload, parallel: boolean = false) { textInNamedTab1: 'textInNamedTab12', }, number: 2, + json: { + text: 'json2', + number: 2, + boolean: true, + array: [ + { + textInArrayInJson: 'textInArrayInJson2', + }, + ], + }, point: [1, 3], radio: 'option2', relationship: draft2.id, + relationshipHasMany: [manyDraftsID, draft2.id], + relationshipPolymorphic: { + relationTo: draftCollectionSlug, + value: draft2.id, + }, + relationshipHasManyPolymorphic: [ + { + relationTo: 'text', + value: doc1ID, + }, + { + relationTo: draftCollectionSlug, + value: draft2.id, + }, + ], + relationshipHasManyPolymorphic2: [ + { + relationTo: 'text', + value: doc1ID, + }, + { + relationTo: draftCollectionSlug, + value: draft3.id, + }, + ], richtext: generateLexicalData({ mediaID: uploadedImage2, textID: doc2ID, @@ -257,6 +431,7 @@ export async function seed(_payload: Payload, parallel: boolean = false) { textInRow: 'textInRow2', textInUnnamedTab2: 'textInUnnamedTab22', upload: uploadedImage2, + uploadHasMany: [uploadedImage, uploadedImage2], }, depth: 0, }) diff --git a/test/versions/slugs.ts b/test/versions/slugs.ts index b930df706..e8dac96bb 100644 --- a/test/versions/slugs.ts +++ b/test/versions/slugs.ts @@ -15,6 +15,7 @@ export const postCollectionSlug = 'posts' export const diffCollectionSlug = 'diff' export const mediaCollectionSlug = 'media' +export const media2CollectionSlug = 'media2' export const versionCollectionSlug = 'version-posts' @@ -31,6 +32,7 @@ export const collectionSlugs = [ postCollectionSlug, diffCollectionSlug, mediaCollectionSlug, + media2CollectionSlug, versionCollectionSlug, textCollectionSlug, ] diff --git a/tools/scripts/src/generateTranslations/utils/translateText.ts b/tools/scripts/src/generateTranslations/utils/translateText.ts index 3c124f337..7d498810f 100644 --- a/tools/scripts/src/generateTranslations/utils/translateText.ts +++ b/tools/scripts/src/generateTranslations/utils/translateText.ts @@ -5,6 +5,7 @@ const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) dotenv.config({ path: path.resolve(dirname, '../../../../', '.env') }) +dotenv.config({ path: path.resolve(dirname, '../../../../../', '.env') }) type TranslationMessage = { role: 'system' | 'user' @@ -78,7 +79,7 @@ export async function translateText(text: string, targetLang: string) { console.log(' Old text:', text, 'New text:', data.choices[0].message.content.trim()) return data.choices[0].message.content.trim() } else { - console.log(`Could not translate: ${text} in lang: ${targetLang}`) + console.log(`Could not translate: ${text} in lang: ${targetLang}`, data.error) } } } catch (e) {