From 0238ca0e9406149a26689e289730cb6af0f3c3fb Mon Sep 17 00:00:00 2001 From: Lydia Scarf Date: Fri, 8 May 2026 12:07:49 +0200 Subject: [PATCH] fix(#7794): populate relation widget from url The createEmptyDraft action creator didn't combine repeated query params into a List, instead returning the last param. Now relation widgets with isMultiple set will populate correctly. --- .../src/actions/__tests__/entries.spec.js | 37 ++++++++++++++++++- .../decap-cms-core/src/actions/entries.ts | 29 +++++++++++---- packages/decap-cms-core/src/types/redux.ts | 1 + 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/packages/decap-cms-core/src/actions/__tests__/entries.spec.js b/packages/decap-cms-core/src/actions/__tests__/entries.spec.js index 40697cb5ff10..398b72db6057 100644 --- a/packages/decap-cms-core/src/actions/__tests__/entries.spec.js +++ b/packages/decap-cms-core/src/actions/__tests__/entries.spec.js @@ -1,4 +1,4 @@ -import { fromJS, Map } from 'immutable'; +import { fromJS, List, Map } from 'immutable'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; @@ -99,6 +99,41 @@ describe('entries', () => { }); }); + it('should populate draft entry from repeated URL param', () => { + const store = mockStore({ mediaLibrary: fromJS({ files: [] }) }); + + const collection = fromJS({ + fields: [{ name: 'post', multiple: true }], + }); + + return store + .dispatch(createEmptyDraft(collection, '?post=2026-05-07-test&post=2026-05-08-test')) + .then(() => { + const actions = store.getActions(); + expect(actions).toHaveLength(1); + + expect(actions[0]).toEqual({ + payload: { + author: '', + collection: undefined, + data: { post: List(['2026-05-07-test', '2026-05-08-test']) }, + meta: {}, + i18n: {}, + isModification: null, + label: null, + mediaFiles: [], + partial: false, + path: '', + raw: '', + slug: '', + status: '', + updatedOn: '', + }, + type: 'DRAFT_CREATE_EMPTY', + }); + }); + }); + it('should html escape URL params', () => { const store = mockStore({ mediaLibrary: fromJS({ files: [] }) }); diff --git a/packages/decap-cms-core/src/actions/entries.ts b/packages/decap-cms-core/src/actions/entries.ts index f9f52236d93c..67621e2e900d 100644 --- a/packages/decap-cms-core/src/actions/entries.ts +++ b/packages/decap-cms-core/src/actions/entries.ts @@ -1,9 +1,14 @@ -import { fromJS, List, Map } from 'immutable'; +import { fromJS, List, Map, Set } from 'immutable'; import isEqual from 'lodash/isEqual'; import { Cursor } from 'decap-cms-lib-util'; import { selectCollectionEntriesCursor } from '../reducers/cursors'; -import { selectFields, updateFieldByKey, selectDefaultSortField } from '../reducers/collections'; +import { + selectFields, + selectField, + updateFieldByKey, + selectDefaultSortField, +} from '../reducers/collections'; import { selectIntegration, selectPublishedSlugs } from '../reducers'; import { getIntegrationProvider } from '../integrations'; import { currentBackend } from '../backend'; @@ -38,7 +43,6 @@ import type { import type { EntryValue } from '../valueObjects/Entry'; import type { Backend } from '../backend'; import type AssetProxy from '../valueObjects/AssetProxy'; -import type { Set } from 'immutable'; /* * Constant Declarations @@ -740,10 +744,21 @@ function getMetaFields(fields: EntryFields) { export function createEmptyDraft(collection: Collection, search: string) { return async (dispatch: ThunkDispatch, getState: () => State) => { const params = new URLSearchParams(search); - params.forEach((value, key) => { - collection = updateFieldByKey(collection, key, field => - field.set('default', processValue(value)), - ); + const uniqueKeys = Set([...params.keys()]).toArray(); + + uniqueKeys.forEach(key => { + const field = selectField(collection, key); + const isMultiple = field?.get('multiple', false); + const values = params.getAll(key); + + collection = updateFieldByKey(collection, key, field => { + if (isMultiple) { + const allValues = values.flatMap(v => v.split(',')).map(processValue); + return field.set('default', List(allValues)); + } else { + return field.set('default', processValue(values[values.length - 1])); + } + }); }); const fields = collection.get('fields', List()); diff --git a/packages/decap-cms-core/src/types/redux.ts b/packages/decap-cms-core/src/types/redux.ts index 5344c93e4040..37cb34263d8f 100644 --- a/packages/decap-cms-core/src/types/redux.ts +++ b/packages/decap-cms-core/src/types/redux.ts @@ -596,6 +596,7 @@ export type EntryField = StaticallyTypedRecord<{ name: string; default: string | null | boolean | List; media_folder?: string; + multiple?: boolean; public_folder?: string; comment?: string; meta?: boolean;