diff --git a/packages/visual-editor/CHANGELOG.md b/packages/visual-editor/CHANGELOG.md index 54c782a2e3..ce94642ee7 100644 --- a/packages/visual-editor/CHANGELOG.md +++ b/packages/visual-editor/CHANGELOG.md @@ -1,3 +1,74 @@ +#### 1.3.1 (2026-05-19) + +##### Bug Fixes + +- primary locale url normalization ([#1219](https://github.com/yext/visual-editor/pull/1219)) ([f1c1465c](https://github.com/yext/visual-editor/commit/f1c1465c42242fd5bd2318dda8c88fe79c3910cd)) + +#### 1.3.0 (2026-05-18) + +##### Bug Fixes + +- use display name for multiple linked entities warning ([#1217](https://github.com/yext/visual-editor/pull/1217)) ([c253d9ef](https://github.com/yext/visual-editor/commit/c253d9ef3a0c1d2f3648d284eb3247fe53456e34)) + +#### 1.3.0-alpha.2 (2026-05-18) + +##### New Features + +- improve warning message for multiple linked entities ([#1216](https://github.com/yext/visual-editor/pull/1216)) ([517ed851](https://github.com/yext/visual-editor/commit/517ed851aa92ada6b750bd84285073beebe93607)) + +##### Bug Fixes + +- embedded field pickers in mapping are not scoped ([#1215](https://github.com/yext/visual-editor/pull/1215)) ([38be7643](https://github.com/yext/visual-editor/commit/38be7643f9ecc57d664b62c3990eab9094999dce)) + +#### 1.3.0-alpha.1 (2026-05-14) + +##### Chores + +- make toPuckFields a runtime adapter instead of a TS cast ([#1211](https://github.com/yext/visual-editor/pull/1211)) ([360d9e2a](https://github.com/yext/visual-editor/commit/360d9e2a8b08099f58792b320b4ab8297c21bfa5)) +- prevent shell injection in github action (vuln-44055) ([#1185](https://github.com/yext/visual-editor/pull/1185)) ([ebec8fb8](https://github.com/yext/visual-editor/commit/ebec8fb8f7aa02ea8b942a97bc4b52641c62e628)) +- upgrade pnpm to 10.33.0 ([#1171](https://github.com/yext/visual-editor/pull/1171)) ([1507a012](https://github.com/yext/visual-editor/commit/1507a012408950026da5456891db5a614170b423)) + +##### New Features + +- allow editing of the directory card's title ([#1208](https://github.com/yext/visual-editor/pull/1208)) ([838faf32](https://github.com/yext/visual-editor/commit/838faf32e31a45d8686271fe265ac13b7619d4cb)) +- add support for Linked Entity Slots ([#1210](https://github.com/yext/visual-editor/pull/1210)) ([80a193e3](https://github.com/yext/visual-editor/commit/80a193e342691a3d094bfa3799b81619734b64ef)) +- add framework for linked entity mapping ([#1206](https://github.com/yext/visual-editor/pull/1206)) ([a736d119](https://github.com/yext/visual-editor/commit/a736d11927b324760cbd791daea4c95d6278388a)) +- allow boolean display fields in locator ([#1207](https://github.com/yext/visual-editor/pull/1207)) ([5fef9a82](https://github.com/yext/visual-editor/commit/5fef9a822175f250c92585f3311d6ce37ea3d872)) +- add styledImage option for fields ([#1202](https://github.com/yext/visual-editor/pull/1202)) ([a296e4b0](https://github.com/yext/visual-editor/commit/a296e4b0d3029f52b48925730cb5aa02f8769990)) +- add styledPageSection option to field ([#1200](https://github.com/yext/visual-editor/pull/1200)) ([cc682be7](https://github.com/yext/visual-editor/commit/cc682be73c8315f11fdd5b4fab4a000a0c7e7c49)) +- add styledLink option for fields ([#1199](https://github.com/yext/visual-editor/pull/1199)) ([cd5c81d8](https://github.com/yext/visual-editor/commit/cd5c81d8eb2487691ea8fb1d73a1413fa0cbc10a)) +- add styledButton option for fields ([#1198](https://github.com/yext/visual-editor/pull/1198)) ([cf6564c3](https://github.com/yext/visual-editor/commit/cf6564c332abae30b1b1ace622cc173789bd4900)) +- support using custom image for map pin icon in locator ([#1194](https://github.com/yext/visual-editor/pull/1194)) ([9b796aed](https://github.com/yext/visual-editor/commit/9b796aed2ab39cb825102936d00a1e59811110c2)) +- add 'styledText' fieldType ([#1197](https://github.com/yext/visual-editor/pull/1197)) ([f881e026](https://github.com/yext/visual-editor/commit/f881e026bb9256b59bbd92bae74b2f2d91045915)) +- allow Other color option in basicSelector ([#1183](https://github.com/yext/visual-editor/pull/1183)) ([d3bf7394](https://github.com/yext/visual-editor/commit/d3bf7394f1865c994717ae2a8c3043da25a11f62)) +- handle single linked entity field ([#1167](https://github.com/yext/visual-editor/pull/1167)) ([2e76a4be](https://github.com/yext/visual-editor/commit/2e76a4be74ebeb2e7aae56556c1d573a7a80af5a)) + +##### Bug Fixes + +- use ids to map categories to schema ([#1187](https://github.com/yext/visual-editor/pull/1187)) ([77cdb111](https://github.com/yext/visual-editor/commit/77cdb11163e16e869506b9132bd50bb3a5021d5b)) +- images in Grid would stretch to fit the container ([#1204](https://github.com/yext/visual-editor/pull/1204)) ([54bd67c8](https://github.com/yext/visual-editor/commit/54bd67c8ad24da71702a6e822bb7155e34ba42b4)) +- hide components with missing entity data ([#1159](https://github.com/yext/visual-editor/pull/1159)) ([1f82905a](https://github.com/yext/visual-editor/commit/1f82905aa577487f277ceb15a443ea0464231059)) +- footer logo migration ([#1193](https://github.com/yext/visual-editor/pull/1193)) ([9aee0a65](https://github.com/yext/visual-editor/commit/9aee0a65a4ace3bcb1830ef1b945a3426bb1ddd1)) +- handle product price struct ([#1175](https://github.com/yext/visual-editor/pull/1175)) ([67fd06ca](https://github.com/yext/visual-editor/commit/67fd06ca54f9cdae107455760056bd0662538de6)) +- localStorage history state not loading ([#1173](https://github.com/yext/visual-editor/pull/1173)) ([73592275](https://github.com/yext/visual-editor/commit/7359227574c603c9b440290b019d1477b89e0a82)) +- cta outline variant colors ([#1165](https://github.com/yext/visual-editor/pull/1165)) ([f9c30fef](https://github.com/yext/visual-editor/commit/f9c30feff4d87e81ebcd8c0c5a2adc9e6f8e8d80)) + +##### Refactors + +- remove YextField ([#1201](https://github.com/yext/visual-editor/pull/1201)) ([37f1f542](https://github.com/yext/visual-editor/commit/37f1f542b96b23004693ef478b0821755c2a0ed2)) +- add entityField type ([#1196](https://github.com/yext/visual-editor/pull/1196)) ([a3fe737e](https://github.com/yext/visual-editor/commit/a3fe737ea4bf230cd38e25de33de6ea398b4efe5)) +- update dynamicOptionsSelector ([#1195](https://github.com/yext/visual-editor/pull/1195)) ([86b14389](https://github.com/yext/visual-editor/commit/86b143899be8da63daec69668ed3201bd427a7a6)) +- add image fieldType ([#1184](https://github.com/yext/visual-editor/pull/1184)) ([9ebdce19](https://github.com/yext/visual-editor/commit/9ebdce19c93425b33d6ea44cab62dc36b44c8d5e)) +- use 'basicSelector' for maxWidth ([#1192](https://github.com/yext/visual-editor/pull/1192)) ([23e21bc3](https://github.com/yext/visual-editor/commit/23e21bc3d784033456acc425ac7421cd315409a7)) +- update callers to use the 'code' fieldType ([#1191](https://github.com/yext/visual-editor/pull/1191)) ([3ae2db9a](https://github.com/yext/visual-editor/commit/3ae2db9a63927c7e70c880fcf5322a9e9d2891d8)) +- add 'translatableString' fieldType ([#1190](https://github.com/yext/visual-editor/pull/1190)) ([be8bf3a9](https://github.com/yext/visual-editor/commit/be8bf3a92cfc92998f8f6f40f4243cf057b36536)) +- remove video from YextField ([#1186](https://github.com/yext/visual-editor/pull/1186)) ([4ffb1402](https://github.com/yext/visual-editor/commit/4ffb14022dbfde632f7c35c456ba6e1248c228cd)) +- remove text from YextField ([#1182](https://github.com/yext/visual-editor/pull/1182)) ([f633ff8b](https://github.com/yext/visual-editor/commit/f633ff8ba3a798af4e672bfbb6189e0c0ce398ad)) +- remove radio from YextField ([#1179](https://github.com/yext/visual-editor/pull/1179)) ([cd397a29](https://github.com/yext/visual-editor/commit/cd397a294f83bf9b190bfe22e2e80305dad5068e)) +- add 'optionalNumber' a fieldType ([#1174](https://github.com/yext/visual-editor/pull/1174)) ([cba35157](https://github.com/yext/visual-editor/commit/cba3515713b8bc16bae5f6cb1eac985625c86d23)) +- add 'ctaSelector' fieldType ([#1170](https://github.com/yext/visual-editor/pull/1170)) ([143da776](https://github.com/yext/visual-editor/commit/143da7766c4435b58df7d29f1e1c71736347e7f4)) +- use 'basicSelector' everywhere ([#1166](https://github.com/yext/visual-editor/pull/1166)) ([b7c0370e](https://github.com/yext/visual-editor/commit/b7c0370ea692e80d7c7f4fe645b8c790a2ea4110)) + #### 1.2.2 (2026-04-17) ##### Chores diff --git a/packages/visual-editor/locales/platform/cs/visual-editor.json b/packages/visual-editor/locales/platform/cs/visual-editor.json index 46e0d977e4..be6f1959a3 100644 --- a/packages/visual-editor/locales/platform/cs/visual-editor.json +++ b/packages/visual-editor/locales/platform/cs/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "Skupina CTA", "customCodeSection": "Vlastní sekce kódu", "directory": "Adresář", + "directoryChildren": "Adresář Děti", "directoryGrid": "Adresářová mřížka", "emails": "E -maily", "eventCard": "Karta událostí", @@ -563,7 +564,7 @@ "knowledgeGraphContent": "Obsah grafu znalostí", "link": "odkaz", "linkedEntityFields": "Pole propojených entit", - "linkedEntityMultiValueWarning": "Pro {{fieldName}} bylo nalezeno více propojených entit. Pomocí první propojené entity.", + "linkedEntityMultiValueWarning": "{{linkedField}} obsahuje několik propojených entit. Používá se první pro {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Klikněte na web", "drivingDirections": "Směry jízdy", diff --git a/packages/visual-editor/locales/platform/da/visual-editor.json b/packages/visual-editor/locales/platform/da/visual-editor.json index 2bf19088d5..c4f7a56567 100644 --- a/packages/visual-editor/locales/platform/da/visual-editor.json +++ b/packages/visual-editor/locales/platform/da/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA gruppe", "customCodeSection": "Brugerdefineret kodesektion", "directory": "Vejviser", + "directoryChildren": "Vejviser børn", "directoryGrid": "Kataloggitter", "emails": "E -mails", "eventCard": "Begivenhedskort", @@ -561,7 +562,7 @@ "knowledgeGraphContent": "Videngrafindhold", "link": "forbindelse", "linkedEntityFields": "Tilknyttede enhedsfelter", - "linkedEntityMultiValueWarning": "Der blev fundet flere sammenkædede enheder for {{fieldName}}. Brug af den første linkede enhed.", + "linkedEntityMultiValueWarning": "{{linkedField}} indeholder flere sammenkædede enheder. Brug af den første til {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Klik på webstedet", "drivingDirections": "Kørselsvejledning", diff --git a/packages/visual-editor/locales/platform/de/visual-editor.json b/packages/visual-editor/locales/platform/de/visual-editor.json index 81d8dc28a3..e1fdc475e1 100644 --- a/packages/visual-editor/locales/platform/de/visual-editor.json +++ b/packages/visual-editor/locales/platform/de/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA-Gruppe", "customCodeSection": "Custom-Code-Bereich", "directory": "Verzeichnis", + "directoryChildren": "Verzeichnis Kinder", "directoryGrid": "Verzeichnis-Grid", "emails": "E-Mails", "eventCard": "Events", @@ -561,7 +562,7 @@ "knowledgeGraphContent": "Knowledge Graph Inhalt", "link": "Link", "linkedEntityFields": "Verknüpfte Entitätsfelder", - "linkedEntityMultiValueWarning": "Für {{fieldName}} wurden mehrere verknüpfte Entitäten gefunden. Verwendung der ersten verknüpften Entität.", + "linkedEntityMultiValueWarning": "{{linkedField}} enthält mehrere verknüpfte Entitäten. Verwenden des ersten für {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Klick zur Website", "drivingDirections": "Routenplaner", diff --git a/packages/visual-editor/locales/platform/en-GB/visual-editor.json b/packages/visual-editor/locales/platform/en-GB/visual-editor.json index 316aa6c3fb..1f00ff43f8 100644 --- a/packages/visual-editor/locales/platform/en-GB/visual-editor.json +++ b/packages/visual-editor/locales/platform/en-GB/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA Group", "customCodeSection": "Custom Code Section", "directory": "Directory", + "directoryChildren": "Directory Children", "directoryGrid": "Directory Grid", "emails": "Emails", "eventCard": "Event Card", @@ -561,7 +562,7 @@ "knowledgeGraphContent": "Knowledge Graph content", "link": "link", "linkedEntityFields": "Linked Entity Fields", - "linkedEntityMultiValueWarning": "Multiple linked entities were found for {{fieldName}}. Using the first linked entity.", + "linkedEntityMultiValueWarning": "{{linkedField}} contains multiple linked entities. Using the first one for {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Click to Website", "drivingDirections": "Driving Directions", diff --git a/packages/visual-editor/locales/platform/en/visual-editor.json b/packages/visual-editor/locales/platform/en/visual-editor.json index 76c2399bfb..17935d4d42 100644 --- a/packages/visual-editor/locales/platform/en/visual-editor.json +++ b/packages/visual-editor/locales/platform/en/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA Group", "customCodeSection": "Custom Code Section", "directory": "Directory", + "directoryChildren": "Directory Children", "directoryGrid": "Directory Grid", "emails": "Emails", "eventCard": "Event Card", @@ -561,7 +562,7 @@ "knowledgeGraphContent": "Knowledge Graph content", "link": "Link", "linkedEntityFields": "Linked Entity Fields", - "linkedEntityMultiValueWarning": "Multiple linked entities were found for {{fieldName}}. Using the first linked entity.", + "linkedEntityMultiValueWarning": "{{linkedField}} contains multiple linked entities. Using the first one for {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Click to Website", "drivingDirections": "Driving Directions", diff --git a/packages/visual-editor/locales/platform/es/visual-editor.json b/packages/visual-editor/locales/platform/es/visual-editor.json index e305a97cf6..ca12e09cc6 100644 --- a/packages/visual-editor/locales/platform/es/visual-editor.json +++ b/packages/visual-editor/locales/platform/es/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "Grupo CTA", "customCodeSection": "Sección de código personalizado", "directory": "Directorio", + "directoryChildren": "Directorio Niños", "directoryGrid": "Cuadrícula de directorio", "emails": "Correos electrónicos", "eventCard": "Tarjeta de evento", @@ -562,7 +563,7 @@ "knowledgeGraphContent": "Contenido del gráfico de conocimiento", "link": "enlace", "linkedEntityFields": "Campos de entidad vinculados", - "linkedEntityMultiValueWarning": "Se encontraron varias entidades vinculadas para {{fieldName}}. Usando la primera entidad vinculada.", + "linkedEntityMultiValueWarning": "{{linkedField}} contiene varias entidades vinculadas. Usando el primero para {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Haga clic en el sitio web", "drivingDirections": "Instrucciones de conducción", diff --git a/packages/visual-editor/locales/platform/et/visual-editor.json b/packages/visual-editor/locales/platform/et/visual-editor.json index ede064cdd8..378e954c7e 100644 --- a/packages/visual-editor/locales/platform/et/visual-editor.json +++ b/packages/visual-editor/locales/platform/et/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA rühm", "customCodeSection": "Kohandatud koodisektsioon", "directory": "Kataloog", + "directoryChildren": "Kataloog Lapsed", "directoryGrid": "Kataloogivõrk", "emails": "E -kirjad", "eventCard": "Sündmuse kaart", @@ -561,7 +562,7 @@ "knowledgeGraphContent": "Teadmiste graafi sisu", "link": "link", "linkedEntityFields": "Lingitud olemiväljad", - "linkedEntityMultiValueWarning": "{{fieldName}} jaoks leiti mitu lingitud olemit. Kasutades esimest lingitud olemit.", + "linkedEntityMultiValueWarning": "{{linkedField}} sisaldab mitut lingitud olemit. Kasutades esimest {{resolvedField}} jaoks.", "linkTypes": { "clickToWebsite": "Klõpsake veebisaidil", "drivingDirections": "Juhtimissuunad", diff --git a/packages/visual-editor/locales/platform/fi/visual-editor.json b/packages/visual-editor/locales/platform/fi/visual-editor.json index 226c462eaf..e5ebec6ac8 100644 --- a/packages/visual-editor/locales/platform/fi/visual-editor.json +++ b/packages/visual-editor/locales/platform/fi/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA -ryhmä", "customCodeSection": "Mukautettu koodiosa", "directory": "Hakemisto", + "directoryChildren": "Hakemisto Lapset", "directoryGrid": "Hakemistoruudukko", "emails": "Sähköpostit", "eventCard": "Tapahtumakortti", @@ -561,7 +562,7 @@ "knowledgeGraphContent": "Tietokaavion sisältö", "link": "linkki", "linkedEntityFields": "Linkitetyt entiteettikentät", - "linkedEntityMultiValueWarning": "Kohdalle {{fieldName}} löydettiin useita linkitettyjä entiteettejä. Ensimmäisen linkitetyn entiteetin käyttäminen.", + "linkedEntityMultiValueWarning": "{{linkedField}} sisältää useita linkitettyjä kokonaisuuksia. Ensimmäistä käytetään mallille {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Napsauta verkkosivustoa", "drivingDirections": "Ajo -ohjeet", diff --git a/packages/visual-editor/locales/platform/fr/visual-editor.json b/packages/visual-editor/locales/platform/fr/visual-editor.json index 82a08509f4..6fc35bbdf7 100644 --- a/packages/visual-editor/locales/platform/fr/visual-editor.json +++ b/packages/visual-editor/locales/platform/fr/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "Groupe CTA", "customCodeSection": "Section de code personnalisé", "directory": "Annuaire", + "directoryChildren": "Annuaire Enfants", "directoryGrid": "Grille de répertoire", "emails": "E-mails", "eventCard": "Carte d'événement", @@ -562,7 +563,7 @@ "knowledgeGraphContent": "Contenu du graphique de connaissances", "link": "lien", "linkedEntityFields": "Champs d'entité liée", - "linkedEntityMultiValueWarning": "Plusieurs entités liées ont été trouvées pour {{fieldName}}. Utilisation de la première entité liée.", + "linkedEntityMultiValueWarning": "{{linkedField}} contient plusieurs entités liées. Utilisation du premier pour {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Cliquez sur le site Web", "drivingDirections": "Itinéraires", diff --git a/packages/visual-editor/locales/platform/hr/visual-editor.json b/packages/visual-editor/locales/platform/hr/visual-editor.json index 68441d2a2d..8bbc4ac0a2 100644 --- a/packages/visual-editor/locales/platform/hr/visual-editor.json +++ b/packages/visual-editor/locales/platform/hr/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA grupa", "customCodeSection": "Odjeljak prilagođenog koda", "directory": "Imenik", + "directoryChildren": "Imenik Djeca", "directoryGrid": "Rešetka imenika", "emails": "E -mailovi", "eventCard": "Kartica događaja", @@ -562,7 +563,7 @@ "knowledgeGraphContent": "Sadržaj grafikona znanja", "link": "link", "linkedEntityFields": "Polja povezanih entiteta", - "linkedEntityMultiValueWarning": "Pronađeno je više povezanih entiteta za {{fieldName}}. Korištenje prvog povezanog entiteta.", + "linkedEntityMultiValueWarning": "{{linkedField}} sadrži više povezanih entiteta. Korištenje prvog za {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Kliknite na web mjesto", "drivingDirections": "Upute za vožnju", diff --git a/packages/visual-editor/locales/platform/hu/visual-editor.json b/packages/visual-editor/locales/platform/hu/visual-editor.json index 797abb9eb4..ff56f32e21 100644 --- a/packages/visual-editor/locales/platform/hu/visual-editor.json +++ b/packages/visual-editor/locales/platform/hu/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA -csoport", "customCodeSection": "Egyedi kód szakasz", "directory": "Könyvtár", + "directoryChildren": "Címtár Gyermekek", "directoryGrid": "Címtárrács", "emails": "E -mailek", "eventCard": "Rendezvénykártya", @@ -561,7 +562,7 @@ "knowledgeGraphContent": "Tudás grafikon tartalma", "link": "link", "linkedEntityFields": "Kapcsolt entitásmezők", - "linkedEntityMultiValueWarning": "Több összekapcsolt entitás található a következőhöz: {{fieldName}}. Az első kapcsolt entitás használata.", + "linkedEntityMultiValueWarning": "A {{linkedField}} több összekapcsolt entitást tartalmaz. Az első használata a következőhöz: {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Kattintson a weboldalra", "drivingDirections": "Vezetési útmutatások", diff --git a/packages/visual-editor/locales/platform/it/visual-editor.json b/packages/visual-editor/locales/platform/it/visual-editor.json index d2a91656ed..5edd9106da 100644 --- a/packages/visual-editor/locales/platform/it/visual-editor.json +++ b/packages/visual-editor/locales/platform/it/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "Gruppo CTA", "customCodeSection": "Sezione di codice personalizzato", "directory": "Directory", + "directoryChildren": "Directory Bambini", "directoryGrid": "Griglia delle directory", "emails": "E -mail", "eventCard": "Carta Evento", @@ -562,7 +563,7 @@ "knowledgeGraphContent": "Contenuto del grafico della conoscenza", "link": "collegamento", "linkedEntityFields": "Campi di entità collegate", - "linkedEntityMultiValueWarning": "Sono state trovate più entità collegate per {{fieldName}}. Utilizzando la prima entità collegata.", + "linkedEntityMultiValueWarning": "{{linkedField}} contiene più entità collegate. Utilizzando il primo per {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Fai clic sul sito Web", "drivingDirections": "Indicazioni stradali", diff --git a/packages/visual-editor/locales/platform/ja/visual-editor.json b/packages/visual-editor/locales/platform/ja/visual-editor.json index d226db2255..8e4999861b 100644 --- a/packages/visual-editor/locales/platform/ja/visual-editor.json +++ b/packages/visual-editor/locales/platform/ja/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTAグループ", "customCodeSection": "カスタムコードセクション", "directory": "ディレクトリ", + "directoryChildren": "ディレクトリの子", "directoryGrid": "ディレクトリグリッド", "emails": "メール", "eventCard": "イベントカード", @@ -561,7 +562,7 @@ "knowledgeGraphContent": "ナレッジグラフコンテンツ", "link": "リンク", "linkedEntityFields": "リンクされたエンティティフィールド", - "linkedEntityMultiValueWarning": "{{fieldName}} に対して複数のリンクされたエンティティが見つかりました。最初にリンクされたエンティティを使用します。", + "linkedEntityMultiValueWarning": "{{linkedField}} には、複数のリンクされたエンティティが含まれています。最初のものを {{resolvedField}} に使用します。", "linkTypes": { "clickToWebsite": "Webサイトをクリックしてください", "drivingDirections": "運転方向", diff --git a/packages/visual-editor/locales/platform/lt/visual-editor.json b/packages/visual-editor/locales/platform/lt/visual-editor.json index a9391dbdb4..22422aab42 100644 --- a/packages/visual-editor/locales/platform/lt/visual-editor.json +++ b/packages/visual-editor/locales/platform/lt/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA grupė", "customCodeSection": "Pasirinktinio kodo skyrius", "directory": "Katalogas", + "directoryChildren": "Katalogas Vaikai", "directoryGrid": "Katalogų tinklelis", "emails": "El. El. laiškai", "eventCard": "Renginio kortelė", @@ -563,7 +564,7 @@ "knowledgeGraphContent": "Žinių grafiko turinys", "link": "nuoroda", "linkedEntityFields": "Susieti esybių laukai", - "linkedEntityMultiValueWarning": "Rasti keli susieti objektai, skirti {{fieldName}}. Naudojant pirmąjį susietą objektą.", + "linkedEntityMultiValueWarning": "{{linkedField}} yra keli susieti objektai. Naudojamas pirmasis, skirtas {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Spustelėkite svetainę", "drivingDirections": "Važiavimo kryptys", diff --git a/packages/visual-editor/locales/platform/lv/visual-editor.json b/packages/visual-editor/locales/platform/lv/visual-editor.json index 6df1c28a35..d833d721f4 100644 --- a/packages/visual-editor/locales/platform/lv/visual-editor.json +++ b/packages/visual-editor/locales/platform/lv/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA grupa", "customCodeSection": "Pielāgota koda sadaļa", "directory": "Direktors", + "directoryChildren": "Katalogs Bērni", "directoryGrid": "Direktoriju režģis", "emails": "E -pasti", "eventCard": "Pasākuma karte", @@ -561,7 +562,7 @@ "knowledgeGraphContent": "Zināšanu grafika saturs", "link": "saite", "linkedEntityFields": "Saistītie entītiju lauki", - "linkedEntityMultiValueWarning": "{{fieldName}} tika atrastas vairākas saistītās entītijas. Izmantojot pirmo saistīto entītiju.", + "linkedEntityMultiValueWarning": "{{linkedField}} satur vairākas saistītas entītijas. Izmantojot pirmo {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Noklikšķiniet uz vietni", "drivingDirections": "Braukšanas norādījumi", diff --git a/packages/visual-editor/locales/platform/nb/visual-editor.json b/packages/visual-editor/locales/platform/nb/visual-editor.json index 057ed84e74..a622e34fa3 100644 --- a/packages/visual-editor/locales/platform/nb/visual-editor.json +++ b/packages/visual-editor/locales/platform/nb/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA gruppe", "customCodeSection": "Tilpasset kodeseksjon", "directory": "Katalog", + "directoryChildren": "Katalog barn", "directoryGrid": "Katalognett", "emails": "E-post", "eventCard": "Eventkort", @@ -561,7 +562,7 @@ "knowledgeGraphContent": "Kunnskapsgrafinnhold", "link": "lenke", "linkedEntityFields": "Koblede enhetsfelt", - "linkedEntityMultiValueWarning": "Det ble funnet flere koblede enheter for {{fieldName}}. Bruker den første koblede enheten.", + "linkedEntityMultiValueWarning": "{{linkedField}} inneholder flere koblede enheter. Bruker den første for {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Klikk til nettstedet", "drivingDirections": "Veibeskrivelse", diff --git a/packages/visual-editor/locales/platform/nl/visual-editor.json b/packages/visual-editor/locales/platform/nl/visual-editor.json index 0dc85402d9..ef16461f86 100644 --- a/packages/visual-editor/locales/platform/nl/visual-editor.json +++ b/packages/visual-editor/locales/platform/nl/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA -groep", "customCodeSection": "Aangepaste codesectie", "directory": "Directory", + "directoryChildren": "Adresboek Kinderen", "directoryGrid": "Directoryraster", "emails": "E -mails", "eventCard": "Evenementkaart", @@ -561,7 +562,7 @@ "knowledgeGraphContent": "Kennisgrafiekinhoud", "link": "link", "linkedEntityFields": "Gekoppelde entiteitsvelden", - "linkedEntityMultiValueWarning": "Er zijn meerdere gekoppelde entiteiten gevonden voor {{fieldName}}. Met behulp van de eerste gekoppelde entiteit.", + "linkedEntityMultiValueWarning": "{{linkedField}} bevat meerdere gekoppelde entiteiten. Gebruik de eerste voor {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Klik op de website", "drivingDirections": "Routebeschrijving", diff --git a/packages/visual-editor/locales/platform/pl/visual-editor.json b/packages/visual-editor/locales/platform/pl/visual-editor.json index 1a1e6e4166..79aa71dce3 100644 --- a/packages/visual-editor/locales/platform/pl/visual-editor.json +++ b/packages/visual-editor/locales/platform/pl/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "Grupa CTA", "customCodeSection": "Sekcja kodu niestandardowego", "directory": "Informator", + "directoryChildren": "Katalog Dzieci", "directoryGrid": "Siatka katalogów", "emails": "E -maile", "eventCard": "Karta wydarzenia", @@ -563,7 +564,7 @@ "knowledgeGraphContent": "Treść wykresu wiedzy", "link": "Link", "linkedEntityFields": "Pola encji połączonych", - "linkedEntityMultiValueWarning": "Znaleziono wiele połączonych jednostek dla {{fieldName}}. Korzystanie z pierwszego połączonego elementu.", + "linkedEntityMultiValueWarning": "{{linkedField}} zawiera wiele połączonych jednostek. Używanie pierwszego dla {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Kliknij na stronę internetową", "drivingDirections": "Kierunki jazdy", diff --git a/packages/visual-editor/locales/platform/pt/visual-editor.json b/packages/visual-editor/locales/platform/pt/visual-editor.json index 13d93b5b8d..f428b54fa1 100644 --- a/packages/visual-editor/locales/platform/pt/visual-editor.json +++ b/packages/visual-editor/locales/platform/pt/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "Grupo CTA", "customCodeSection": "Seção de código personalizado", "directory": "Diretório", + "directoryChildren": "Diretório Filhos", "directoryGrid": "Grade de diretório", "emails": "E -mails", "eventCard": "Cartão de Evento", @@ -562,7 +563,7 @@ "knowledgeGraphContent": "Conteúdo do gráfico de conhecimento", "link": "link", "linkedEntityFields": "Campos de entidade vinculada", - "linkedEntityMultiValueWarning": "Várias entidades vinculadas foram encontradas para {{fieldName}}. Usando a primeira entidade vinculada.", + "linkedEntityMultiValueWarning": "{{linkedField}} contém várias entidades vinculadas. Usando o primeiro para {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Clique no site", "drivingDirections": "Direções de direção", diff --git a/packages/visual-editor/locales/platform/ro/visual-editor.json b/packages/visual-editor/locales/platform/ro/visual-editor.json index c0192ab297..8aaec88e3c 100644 --- a/packages/visual-editor/locales/platform/ro/visual-editor.json +++ b/packages/visual-editor/locales/platform/ro/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "Grup CTA", "customCodeSection": "Secțiune de cod personalizat", "directory": "Director", + "directoryChildren": "Director Copii", "directoryGrid": "Director Grid", "emails": "E -mailuri", "eventCard": "Card de eveniment", @@ -562,7 +563,7 @@ "knowledgeGraphContent": "Conținut grafic de cunoștințe", "link": "legătură", "linkedEntityFields": "Câmpurile de entitate conectate", - "linkedEntityMultiValueWarning": "Au fost găsite mai multe entități conectate pentru {{fieldName}}. Folosind prima entitate conectată.", + "linkedEntityMultiValueWarning": "{{linkedField}} conține mai multe entități legate. Folosind primul pentru {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Faceți clic pe site -ul web", "drivingDirections": "Direcții de conducere", diff --git a/packages/visual-editor/locales/platform/sk/visual-editor.json b/packages/visual-editor/locales/platform/sk/visual-editor.json index 51e03260fc..28dd9477ae 100644 --- a/packages/visual-editor/locales/platform/sk/visual-editor.json +++ b/packages/visual-editor/locales/platform/sk/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "Skupina CTA", "customCodeSection": "Vlastná sekcia kódu", "directory": "Adresár", + "directoryChildren": "Adresár Deti", "directoryGrid": "Mriežka adresára", "emails": "E-maily", "eventCard": "Karta udalosti", @@ -563,7 +564,7 @@ "knowledgeGraphContent": "Obsah grafov vedomostí", "link": "prepojiť", "linkedEntityFields": "Polia prepojených entít", - "linkedEntityMultiValueWarning": "Pre {{fieldName}} sa našlo viacero prepojených entít. Pomocou prvej prepojenej entity.", + "linkedEntityMultiValueWarning": "{{linkedField}} obsahuje viacero prepojených entít. Používa sa prvý pre {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Kliknite na webovú stránku", "drivingDirections": "Riadenie", diff --git a/packages/visual-editor/locales/platform/sv/visual-editor.json b/packages/visual-editor/locales/platform/sv/visual-editor.json index 916ff5c6d7..530a4adfde 100644 --- a/packages/visual-editor/locales/platform/sv/visual-editor.json +++ b/packages/visual-editor/locales/platform/sv/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA -grupp", "customCodeSection": "Anpassad kodavsnitt", "directory": "Katalog", + "directoryChildren": "Katalog barn", "directoryGrid": "Katalogrutnät", "emails": "E -postmeddelanden", "eventCard": "Evenemangskort", @@ -561,7 +562,7 @@ "knowledgeGraphContent": "Kunskapgrafinnehåll", "link": "länk", "linkedEntityFields": "Länkade enhetsfält", - "linkedEntityMultiValueWarning": "Flera länkade enheter hittades för {{fieldName}}. Använder den första länkade enheten.", + "linkedEntityMultiValueWarning": "{{linkedField}} innehåller flera länkade enheter. Använder den första för {{resolvedField}}.", "linkTypes": { "clickToWebsite": "Klicka på webbplatsen", "drivingDirections": "Vägbeskrivning", diff --git a/packages/visual-editor/locales/platform/tr/visual-editor.json b/packages/visual-editor/locales/platform/tr/visual-editor.json index fdc23014a5..f83214b0f3 100644 --- a/packages/visual-editor/locales/platform/tr/visual-editor.json +++ b/packages/visual-editor/locales/platform/tr/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA grubu", "customCodeSection": "Özel Kod Bölümü", "directory": "Dizin", + "directoryChildren": "Dizin Çocukları", "directoryGrid": "Dizin Izgarası", "emails": "E -postalar", "eventCard": "Etkinlik Kartı", @@ -561,7 +562,7 @@ "knowledgeGraphContent": "Bilgi Grafik İçeriği", "link": "bağlantı", "linkedEntityFields": "Bağlantılı Varlık Alanları", - "linkedEntityMultiValueWarning": "{{fieldName}} için birden fazla bağlantılı varlık bulundu. İlk bağlantılı varlığın kullanılması.", + "linkedEntityMultiValueWarning": "{{linkedField}} birden fazla bağlantılı varlık içerir. {{resolvedField}} için ilkini kullan.", "linkTypes": { "clickToWebsite": "Web sitesine tıklayın", "drivingDirections": "Yol Tarifi", diff --git a/packages/visual-editor/locales/platform/zh-TW/visual-editor.json b/packages/visual-editor/locales/platform/zh-TW/visual-editor.json index 67064ecb9b..7acd81504b 100644 --- a/packages/visual-editor/locales/platform/zh-TW/visual-editor.json +++ b/packages/visual-editor/locales/platform/zh-TW/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA組", "customCodeSection": "自定義代碼部分", "directory": "目錄", + "directoryChildren": "目錄兒童", "directoryGrid": "目錄網格", "emails": "電子郵件", "eventCard": "活動卡", @@ -561,7 +562,7 @@ "knowledgeGraphContent": "知識圖內容", "link": "鏈接", "linkedEntityFields": "連結實體字段", - "linkedEntityMultiValueWarning": "找到了 {{fieldName}} 的多個連結實體。使用第一個連結實體。", + "linkedEntityMultiValueWarning": "{{linkedField}} 包含多個連結實體。將第一個用於 {{resolvedField}}。", "linkTypes": { "clickToWebsite": "單擊網站", "drivingDirections": "行駛方向", diff --git a/packages/visual-editor/locales/platform/zh/visual-editor.json b/packages/visual-editor/locales/platform/zh/visual-editor.json index d8201dbf77..4131c29b07 100644 --- a/packages/visual-editor/locales/platform/zh/visual-editor.json +++ b/packages/visual-editor/locales/platform/zh/visual-editor.json @@ -68,6 +68,7 @@ "ctaGroup": "CTA组", "customCodeSection": "自定义代码部分", "directory": "目录", + "directoryChildren": "目录儿童", "directoryGrid": "目录网格", "emails": "电子邮件", "eventCard": "活动卡", @@ -561,7 +562,7 @@ "knowledgeGraphContent": "知识图内容", "link": "关联", "linkedEntityFields": "链接实体字段", - "linkedEntityMultiValueWarning": "找到了 {{fieldName}} 的多个链接实体。使用第一个链接实体。", + "linkedEntityMultiValueWarning": "{{linkedField}} 包含多个链接实体。将第一个用于 {{resolvedField}}。", "linkTypes": { "clickToWebsite": "单击网站", "drivingDirections": "行驶方向", diff --git a/packages/visual-editor/package.json b/packages/visual-editor/package.json index fb51391c93..c372a29131 100644 --- a/packages/visual-editor/package.json +++ b/packages/visual-editor/package.json @@ -1,7 +1,7 @@ { "name": "@yext/visual-editor", "description": "Component library for Yext Pages Visual Editor", - "version": "1.2.2", + "version": "1.3.1", "author": "sumo@yext.com", "repository": { "type": "git", diff --git a/packages/visual-editor/src/components/directory/Directory.test.tsx b/packages/visual-editor/src/components/directory/Directory.test.tsx index ca3613489a..ba833652df 100644 --- a/packages/visual-editor/src/components/directory/Directory.test.tsx +++ b/packages/visual-editor/src/components/directory/Directory.test.tsx @@ -207,6 +207,7 @@ const cityDocument = { name: "Galaxy Grill Rosslyn", timezone: "America/New_York", slug: "arlington", + geomodifier: "2nd Floor", }, { address: { @@ -232,6 +233,7 @@ const cityDocument = { mainPhone: "+12025551012", name: "Galaxy Grill Clarendon", timezone: "America/New_York", + geomodifier: "Rooftop", }, { address: { @@ -650,6 +652,153 @@ const tests: ComponentTest[] = [ }, version: 58, }, + { + name: "version 77 - City with custom card titles", + document: cityDocument, + props: { + ...version40Props, + slots: { + ...version40Props.slots, + DirectoryGrid: [ + { + type: "DirectoryGrid", + props: { + data: { + field: "dm_directoryChildren", + constantValueEnabled: false, + constantValue: [], + mappings: { + cardTitle: { + field: "", + constantValueEnabled: true, + constantValue: { + defaultValue: "[[name]] [[geomodifier]]", + }, + }, + }, + }, + styles: { + backgroundColor: { + selectedColor: "white", + contrastingColor: "black", + }, + }, + slots: { + CardSlot: [ + { + type: "DirectoryCard", + props: { + data: { + cardTitle: { + defaultValue: "[[name]]", + }, + }, + styles: { + backgroundColor: { + selectedColor: "white", + contrastingColor: "black", + }, + }, + slots: { + HeadingSlot: [ + { + type: "HeadingTextSlot", + props: { + data: { + text: { + field: "", + constantValueEnabled: true, + constantValue: { + defaultValue: "[[name]]", + }, + }, + }, + styles: { + level: 3, + align: "left", + }, + }, + }, + ], + AddressSlot: [ + { + type: "AddressSlot", + props: { + data: { + address: { + field: "address", + constantValue: { + line1: "", + city: "", + postalCode: "", + countryCode: "", + }, + }, + }, + styles: { + showGetDirectionsLink: true, + showRegion: true, + showCountry: true, + ctaVariant: "link", + }, + }, + }, + ], + PhoneSlot: [ + { + type: "PhoneSlot", + props: { + data: { + number: { + constantValue: "", + field: "mainPhone", + }, + label: { + constantValue: "", + hasLocalizedValue: "true", + field: "", + }, + }, + styles: { + phoneFormat: "domestic", + includePhoneHyperlink: true, + includeIcon: false, + }, + }, + }, + ], + HoursSlot: [ + { + type: "HoursStatusSlot", + props: { + data: { + hours: { + constantValue: {}, + field: "hours", + }, + }, + styles: { + dayOfWeekFormat: "long", + showDayNames: true, + showCurrentStatus: true, + className: + "mb-2 font-semibold font-body-fontFamily text-body-fontSize h-full", + }, + }, + }, + ], + }, + }, + }, + ], + }, + }, + }, + ], + }, + }, + version: 77, + }, ]; describe("Directory", async () => { diff --git a/packages/visual-editor/src/components/directory/Directory.tsx b/packages/visual-editor/src/components/directory/Directory.tsx index 102d5d2693..b242c21775 100644 --- a/packages/visual-editor/src/components/directory/Directory.tsx +++ b/packages/visual-editor/src/components/directory/Directory.tsx @@ -226,6 +226,18 @@ export const Directory: YextComponentConfig = { { type: "DirectoryGrid", props: { + data: { + field: "dm_directoryChildren", + constantValueEnabled: false, + constantValue: [], + mappings: { + cardTitle: { + field: "name", + constantValueEnabled: false, + constantValue: undefined, + }, + }, + }, styles: { backgroundColor: backgroundColors.background1.value, }, diff --git a/packages/visual-editor/src/components/directory/DirectoryCard.tsx b/packages/visual-editor/src/components/directory/DirectoryCard.tsx index fa0c9ee959..efc35c1d09 100644 --- a/packages/visual-editor/src/components/directory/DirectoryCard.tsx +++ b/packages/visual-editor/src/components/directory/DirectoryCard.tsx @@ -12,10 +12,12 @@ import { ThemeColor, } from "../../utils/themeConfigOptions.ts"; import { deepMerge } from "../../utils/themeResolver.ts"; +import { bindSlots } from "../../utils/cardSlots/bindSlots.ts"; import { mergeMeta, resolveUrlTemplateOfChild, } from "../../utils/urls/resolveUrlTemplate.ts"; +import { TranslatableString } from "../../types/types.ts"; import { Background } from "../atoms/background.tsx"; import { MaybeLink } from "../atoms/maybeLink.tsx"; import { AddressProps } from "../contentBlocks/Address.tsx"; @@ -29,154 +31,190 @@ import { useDirectoryChildren, } from "./directoryChildReference.tsx"; import { YextComponentConfig, YextFields } from "../../fields/fields.ts"; +import { YextEntityField } from "../../editor/yextEntityFieldUtils.ts"; + +const defaultCardTitle: YextEntityField = { + field: "name", + constantValue: { defaultValue: "[[name]]" }, + constantValueEnabled: false, +}; + +const isHeadingTextField = ( + value: unknown +): value is HeadingTextProps["data"]["text"] => + typeof value === "object" && + value !== null && + ("field" in value || "constantValue" in value); export const defaultDirectoryCardSlotData = ( id: string, index: number, - childRef: DirectoryChildReference, + childRef?: DirectoryChildReference, existingCardStyle?: DirectoryCardProps["styles"], existingSlots?: DirectoryCardProps["slots"] -) => ({ - type: "DirectoryCard", - props: { - id, - index, - styles: { - backgroundColor: - existingCardStyle?.backgroundColor ?? - backgroundColors.background1.value, - }, - slots: { - HeadingSlot: [ - { - type: "HeadingTextSlot", - props: { - ...(id && { id: `${id}-heading` }), - data: { - text: { - constantValue: "", - field: "name", +) => { + const existingHeadingText = + existingSlots?.HeadingSlot?.[0]?.props?.data?.text; + const headingTextField = isHeadingTextField(existingHeadingText) + ? existingHeadingText + : { + field: "", + constantValue: existingHeadingText ?? defaultCardTitle.constantValue, + constantValueEnabled: true, + }; + + return { + type: "DirectoryCard", + props: { + id, + index, + data: { + cardTitle: defaultCardTitle, + }, + styles: { + backgroundColor: + existingCardStyle?.backgroundColor ?? + backgroundColors.background1.value, + }, + slots: { + HeadingSlot: [ + { + type: "HeadingTextSlot", + props: { + ...(id && { id: `${id}-heading` }), + data: { + text: headingTextField, }, - }, - styles: { - level: existingSlots?.HeadingSlot?.[0]?.props?.styles?.level ?? 3, - align: - existingSlots?.HeadingSlot?.[0]?.props?.styles?.align ?? "left", - }, - parentData: { - field: "profile.name", - }, - } satisfies HeadingTextProps, - }, - ], - AddressSlot: [ - { - type: "AddressSlot", - props: { - ...(id && { id: `${id}-address` }), - data: { - address: { - field: "address", - constantValue: { - line1: "", - city: "", - postalCode: "", - countryCode: "", + styles: { + level: + existingSlots?.HeadingSlot?.[0]?.props?.styles?.level ?? 3, + align: + existingSlots?.HeadingSlot?.[0]?.props?.styles?.align ?? + "left", + }, + } satisfies HeadingTextProps, + }, + ], + AddressSlot: [ + { + type: "AddressSlot", + props: { + ...(id && { id: `${id}-address` }), + data: { + address: { + field: "address", + constantValue: { + line1: "", + city: "", + postalCode: "", + countryCode: "", + }, }, }, - }, - styles: { - showRegion: - existingSlots?.AddressSlot?.[0]?.props?.styles?.showRegion ?? - true, - showCountry: - existingSlots?.AddressSlot?.[0]?.props?.styles?.showCountry ?? - true, - showGetDirectionsLink: - existingSlots?.AddressSlot?.[0]?.props?.styles - ?.showGetDirectionsLink ?? false, - ctaVariant: - existingSlots?.AddressSlot?.[0]?.props?.styles?.ctaVariant ?? - "link", - }, - parentData: { - field: "profile.address", - }, - } satisfies AddressProps, - }, - ], - PhoneSlot: [ - { - type: "PhoneSlot", - props: { - ...(id && { id: `${id}-phone` }), - data: { - number: { - constantValue: "", - field: "mainPhone", + styles: { + showRegion: + existingSlots?.AddressSlot?.[0]?.props?.styles?.showRegion ?? + true, + showCountry: + existingSlots?.AddressSlot?.[0]?.props?.styles?.showCountry ?? + true, + showGetDirectionsLink: + existingSlots?.AddressSlot?.[0]?.props?.styles + ?.showGetDirectionsLink ?? false, + ctaVariant: + existingSlots?.AddressSlot?.[0]?.props?.styles?.ctaVariant ?? + "link", }, - label: { - constantValue: "", - hasLocalizedValue: "true", - field: "", + parentData: { + field: "profile.address", }, - }, - styles: { - phoneFormat: - existingSlots?.PhoneSlot?.[0]?.props?.styles?.phoneFormat ?? - "domestic", - includePhoneHyperlink: - existingSlots?.PhoneSlot?.[0]?.props?.styles - ?.includePhoneHyperlink ?? true, - includeIcon: - existingSlots?.PhoneSlot?.[0]?.props?.styles?.includeIcon ?? - false, - }, - parentData: { - field: "profile.mainPhone", - }, - } satisfies PhoneProps, - }, - ], - HoursSlot: [ - { - type: "HoursStatusSlot", - props: { - ...(id && { id: `${id}-hours` }), - data: { - hours: { - constantValue: {}, - field: "hours", + } satisfies AddressProps, + }, + ], + PhoneSlot: [ + { + type: "PhoneSlot", + props: { + ...(id && { id: `${id}-phone` }), + data: { + number: { + constantValue: "", + field: "mainPhone", + }, + label: { + constantValue: "", + hasLocalizedValue: "true", + field: "", + }, }, - }, - styles: { - dayOfWeekFormat: - existingSlots?.HoursSlot?.[0]?.props?.styles?.dayOfWeekFormat ?? - "long", - showDayNames: - existingSlots?.HoursSlot?.[0]?.props?.styles?.showDayNames ?? - true, - showCurrentStatus: - existingSlots?.HoursSlot?.[0]?.props?.styles - ?.showCurrentStatus ?? true, - className: - existingSlots?.HoursSlot?.[0]?.props?.styles?.className ?? - "mb-2 font-semibold font-body-fontFamily text-body-fontSize h-full", - }, + styles: { + phoneFormat: + existingSlots?.PhoneSlot?.[0]?.props?.styles?.phoneFormat ?? + "domestic", + includePhoneHyperlink: + existingSlots?.PhoneSlot?.[0]?.props?.styles + ?.includePhoneHyperlink ?? true, + includeIcon: + existingSlots?.PhoneSlot?.[0]?.props?.styles?.includeIcon ?? + false, + }, + parentData: { + field: "profile.mainPhone", + }, + } satisfies PhoneProps, + }, + ], + HoursSlot: [ + { + type: "HoursStatusSlot", + props: { + ...(id && { id: `${id}-hours` }), + data: { + hours: { + constantValue: {}, + field: "hours", + }, + }, + styles: { + dayOfWeekFormat: + existingSlots?.HoursSlot?.[0]?.props?.styles + ?.dayOfWeekFormat ?? "long", + showDayNames: + existingSlots?.HoursSlot?.[0]?.props?.styles?.showDayNames ?? + true, + showCurrentStatus: + existingSlots?.HoursSlot?.[0]?.props?.styles + ?.showCurrentStatus ?? true, + className: + existingSlots?.HoursSlot?.[0]?.props?.styles?.className ?? + "mb-2 font-semibold font-body-fontFamily text-body-fontSize h-full", + }, + parentData: { + field: "profile.hours", + }, + } satisfies HoursStatusProps, + }, + ], + }, + ...(childRef + ? { parentData: { - field: "profile.hours", + childRef, }, - } satisfies HoursStatusProps, - }, - ], - }, - parentData: { - childRef, + } + : {}), }, - }, -}); + }; +}; export type DirectoryCardProps = { + /** @internal */ + field?: string; + + data: { + cardTitle: YextEntityField; + }; + /** Styling for all the cards. */ styles: { /** The background color of each directory card */ @@ -281,12 +319,13 @@ const DirectoryCardComponent: PuckComponent = (props) => { AddressSlot: [], }; Object.entries(slotProps).forEach(([key, value]) => { + const nextSlotValue = deepMerge( + { props: { styles: { ...sharedCardProps?.slotStyles?.[key] } } }, + value[0] + ); newSlotData[key as keyof DirectoryCardProps["slots"]] = [ { - ...deepMerge( - { props: { styles: { ...sharedCardProps?.slotStyles?.[key] } } }, - value[0] - ), + ...nextSlotValue, }, ]; }); @@ -301,6 +340,7 @@ const DirectoryCardComponent: PuckComponent = (props) => { type: "DirectoryCard", props: { ...otherProps, + data: props.data, styles: { backgroundColor: sharedCardProps?.cardStyles.backgroundColor || @@ -364,6 +404,22 @@ const DirectoryCardComponent: PuckComponent = (props) => { }; const directoryCardFields: YextFields = { + data: { + label: msg("fields.data", "Data"), + type: "object", + visible: false, + objectFields: { + cardTitle: { + type: "translatableString", + label: msg("fields.title", "Title"), + filter: { + types: ["type.string"], + }, + sourceField: "dm_directoryChildren", + showApplyAllOption: true, + }, + }, + }, styles: { type: "object", label: msg("fields.styles", "Styles"), @@ -391,6 +447,9 @@ export const DirectoryCard: YextComponentConfig = { label: msg("slots.directoryCard", "Directory Card"), fields: directoryCardFields, defaultProps: { + data: { + cardTitle: defaultCardTitle, + }, styles: { backgroundColor: backgroundColors.background1.value, }, @@ -401,5 +460,15 @@ export const DirectoryCard: YextComponentConfig = { AddressSlot: [], }, }, + resolveData: (data) => + bindSlots(data, { + HeadingSlot: + typeof data.props.data?.cardTitle === "string" + ? ({ + field: data.props.field ?? "name", + text: data.props.data.cardTitle, + } satisfies HeadingTextProps["parentData"]) + : undefined, + }), render: (props) => , }; diff --git a/packages/visual-editor/src/components/directory/DirectoryWrapper.tsx b/packages/visual-editor/src/components/directory/DirectoryWrapper.tsx index 168929db43..3b74b45253 100644 --- a/packages/visual-editor/src/components/directory/DirectoryWrapper.tsx +++ b/packages/visual-editor/src/components/directory/DirectoryWrapper.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { PuckComponent, setDeep, Slot } from "@puckeditor/core"; +import { PuckComponent, Slot } from "@puckeditor/core"; import { backgroundColors, ThemeColor, @@ -13,7 +13,10 @@ import { isDirectoryGrid, sortAlphabetically, } from "../../utils/directory/utils.ts"; -import { defaultDirectoryCardSlotData } from "./DirectoryCard.tsx"; +import { + defaultDirectoryCardSlotData, + DirectoryCardProps, +} from "./DirectoryCard.tsx"; import { StreamDocument } from "../../utils/types/StreamDocument.ts"; import { resolveDirectoryListChildren } from "../../utils/urls/resolveDirectoryListChildren.ts"; import { getThemeValue } from "../../utils/getThemeValue.ts"; @@ -22,19 +25,76 @@ import { createDirectoryChildReference, DirectoryChildrenProvider, getSortedDirectoryChildren, - matchesDirectoryChildReference, } from "./directoryChildReference.tsx"; import { YextComponentConfig, YextFields } from "../../fields/fields.ts"; +import { createSlottedItemSource } from "../../utils/itemSource/index.ts"; +import { syncLinkedSlotMappedCards } from "../../utils/cardSlots/slotMappedCards.ts"; +import { resolveComponentData } from "../../utils/resolveComponentData.tsx"; export type DirectoryGridProps = { + data: typeof directoryCardsSource.value; styles: { backgroundColor?: ThemeColor; }; + /** @internal */ + manualSlots?: { + CardSlot: Slot; + }; slots: { CardSlot: Slot; }; }; +const directoryCardsSource = createSlottedItemSource< + DirectoryCardProps["data"], + DirectoryCardProps +>({ + label: msg("components.directoryChildren", "Directory Children"), + itemLabel: "Directory Card", + cardName: "DirectoryCard", + defaultItemProps: () => + defaultDirectoryCardSlotData("DirectoryCard", 0).props, + mappingFields: { + cardTitle: { + type: "entityField", + label: msg("fields.name", "Name"), + filter: { + types: ["type.string"], + }, + }, + }, +}); + +// The linked entity slot helper allows field selection and constant values +// however the directory should be locked to the dm_directoryChildren field. +const getNormalizedDirectoryGridData = ( + value: typeof directoryCardsSource.value | undefined +): typeof directoryCardsSource.value => { + const defaultMappings = (directoryCardsSource.defaultValue.mappings ?? + {}) as NonNullable; + const mappings = (value?.mappings ?? {}) as NonNullable< + typeof directoryCardsSource.defaultValue.mappings + >; + const defaultCardTitle = defaultMappings.cardTitle ?? {}; + + return { + ...directoryCardsSource.defaultValue, + ...value, + field: "dm_directoryChildren", + constantValueEnabled: false, + constantValue: [], + mappings: { + ...defaultMappings, + ...mappings, + cardTitle: { + ...defaultCardTitle, + ...mappings.cardTitle, + field: mappings.cardTitle?.field || "name", + }, + }, + }; +}; + export const DirectoryList = ({ streamDocument, directoryChildren, @@ -120,6 +180,11 @@ export const DirectoryList = ({ }; const directoryGridFields: YextFields = { + data: { + ...directoryCardsSource.field, + disableConstantValueToggle: true, + fixedRepeatedField: "dm_directoryChildren", + }, styles: { type: "object", label: msg("fields.styles", "Styles"), @@ -138,6 +203,13 @@ const directoryGridFields: YextFields = { }, visible: false, }, + manualSlots: { + type: "object", + objectFields: { + CardSlot: { type: "slot", allow: [] }, + }, + visible: false, + }, }; const DirectoryGridWrapper: PuckComponent = (props) => { @@ -171,12 +243,11 @@ export const DirectoryGrid: YextComponentConfig = { label: msg("components.directoryGrid", "Directory Grid"), fields: directoryGridFields, defaultProps: { + ...directoryCardsSource.defaultWrapperProps, + data: getNormalizedDirectoryGridData(directoryCardsSource.defaultValue), styles: { backgroundColor: backgroundColors.background1.value, }, - slots: { - CardSlot: [], - }, }, resolveData: (data, params) => { const streamDocument = params.metadata.streamDocument; @@ -191,39 +262,63 @@ export const DirectoryGrid: YextComponentConfig = { const sortedDirectoryChildren = getSortedDirectoryChildren( streamDocument.dm_directoryChildren ); - - const requiredLength = sortedDirectoryChildren?.length ?? 0; - - // If the current CardSlots match the directory children - // and length is correct, return data with no changes - if ( - data.props.slots.CardSlot.map((card, i) => - matchesDirectoryChildReference( - card.props.parentData?.childRef, - sortedDirectoryChildren[i], - i - ) - ).every((match) => match) && - data.props.slots.CardSlot.length === requiredLength - ) { - return data; - } - - // Update CardSlots data but preserve the existing styles and slot configurations - const updatedCards = Array(requiredLength) - .fill(null) - .map((_, i) => + const normalizedData = getNormalizedDirectoryGridData(data.props.data); + const titleField = normalizedData.mappings?.cardTitle.constantValueEnabled + ? "" + : normalizedData.mappings?.cardTitle.field || "name"; + const titleItems = directoryCardsSource.resolveItems(normalizedData, { + ...streamDocument, + dm_directoryChildren: sortedDirectoryChildren, + }); + const firstCardProps = data.props.slots?.CardSlot?.[0]?.props; + const updatedCards = syncLinkedSlotMappedCards({ + items: sortedDirectoryChildren.map((child, index) => ({ + child, + childIndex: index, + })), + currentCards: data.props.slots.CardSlot, + createCard: (id, index) => defaultDirectoryCardSlotData( - `DirectoryCard-${crypto.randomUUID()}`, - i, - createDirectoryChildReference(sortedDirectoryChildren[i], i), - data.props.slots?.CardSlot?.[0]?.props.styles, - data.props.slots?.CardSlot?.[0]?.props.slots - ) - ); + id, + index, + createDirectoryChildReference(sortedDirectoryChildren[index], index), + firstCardProps?.styles, + firstCardProps?.slots + ), + toParentData: ({ child, childIndex }) => ({ + childRef: createDirectoryChildReference(child, childIndex), + }), + normalizeId: (id) => `DirectoryCard-${id}`, + }).map((card, index) => ({ + ...card, + props: { + ...card.props, + field: titleField, + data: { + cardTitle: + titleItems[index]?.cardTitle !== undefined + ? resolveComponentData( + titleItems[index].cardTitle, + streamDocument.locale || "en", + streamDocument, + { output: "plainText" } + ) + : "[[name]]", + }, + }, + })); - data = setDeep(data, "props.slots.CardSlot", updatedCards); - return data; + return { + ...data, + props: { + ...data.props, + data: normalizedData, + slots: { + ...data.props.slots, + CardSlot: updatedCards, + }, + }, + }; }, render: (props) => , }; diff --git a/packages/visual-editor/src/components/directory/directoryChildReference.test.tsx b/packages/visual-editor/src/components/directory/directoryChildReference.test.tsx index 006594d2f8..775010e8cb 100644 --- a/packages/visual-editor/src/components/directory/directoryChildReference.test.tsx +++ b/packages/visual-editor/src/components/directory/directoryChildReference.test.tsx @@ -77,7 +77,7 @@ const directoryProps = { }; describe("directoryChildReference", () => { - it("stores directory cards as child references and keeps slot bindings field-based", async () => { + it("stores directory cards as child references and keeps card titles in the title slot", async () => { const puckConfig: Config = { components: { Directory, ...SlotsCategoryComponents }, root: { @@ -113,13 +113,27 @@ describe("directoryChildReference", () => { childRef: { childIndex: 0 }, }); expect(firstCard?.props?.parentData?.profile).toBeUndefined(); + expect(firstCard?.props?.data).toEqual({ + cardTitle: "Galaxy Grill Ballston", + }); + expect(firstCard?.props?.field).toBe("name"); + expect(firstCard?.props?.slots?.HeadingSlot?.[0]?.type).toBe( + "HeadingTextSlot" + ); expect( - firstCard?.props?.slots?.HeadingSlot?.[0]?.props?.data?.text?.field - ).toBe("name"); + firstCard?.props?.slots?.HeadingSlot?.[0]?.props?.data?.text + ).toEqual({ + field: "", + constantValue: { + defaultValue: "[[name]]", + }, + constantValueEnabled: true, + }); expect( firstCard?.props?.slots?.HeadingSlot?.[0]?.props?.parentData ).toEqual({ - field: "profile.name", + field: "name", + text: "Galaxy Grill Ballston", }); expect( firstCard?.props?.slots?.AddressSlot?.[0]?.props?.data?.address?.field diff --git a/packages/visual-editor/src/components/migrations/0077_directory_card_title_field.ts b/packages/visual-editor/src/components/migrations/0077_directory_card_title_field.ts new file mode 100644 index 0000000000..07985970ae --- /dev/null +++ b/packages/visual-editor/src/components/migrations/0077_directory_card_title_field.ts @@ -0,0 +1,354 @@ +import { Migration } from "../../utils/migrate.ts"; + +const defaultCardTitle = { defaultValue: "[[name]]" }; + +type LegacySlotChild = { + props?: { + data?: Record; + styles?: Record; + parentData?: unknown; + [key: string]: unknown; + }; + [key: string]: unknown; +}; + +type LegacySlots = { + HeadingSlot?: LegacySlotChild[]; + AddressSlot?: LegacySlotChild[]; + PhoneSlot?: LegacySlotChild[]; + HoursSlot?: LegacySlotChild[]; +}; + +type LegacyDirectoryCardProps = { + id?: string; + index?: number; + styles?: { + backgroundColor?: unknown; + }; + data?: { cardTitle?: unknown }; + slots?: LegacySlots; +}; + +const defaultDirectoryCardSlotData = ( + id: string, + index: number, + existingCardStyle?: LegacyDirectoryCardProps["styles"], + existingSlots?: LegacyDirectoryCardProps["slots"] +) => ({ + type: "DirectoryCard", + props: { + id, + index, + data: { + cardTitle: defaultCardTitle, + }, + styles: { + backgroundColor: existingCardStyle?.backgroundColor ?? { + selectedColor: "white", + contrastingColor: "black", + }, + }, + slots: { + HeadingSlot: [ + { + type: "HeadingTextSlot", + props: { + ...(id && { id: `${id}-heading` }), + data: { + text: + existingSlots?.HeadingSlot?.[0]?.props?.data?.text ?? + defaultCardTitle, + }, + styles: { + level: existingSlots?.HeadingSlot?.[0]?.props?.styles?.level ?? 3, + align: + existingSlots?.HeadingSlot?.[0]?.props?.styles?.align ?? "left", + }, + }, + }, + ], + AddressSlot: [ + { + type: "AddressSlot", + props: { + ...(id && { id: `${id}-address` }), + data: { + address: { + field: "address", + constantValue: { + line1: "", + city: "", + postalCode: "", + countryCode: "", + }, + }, + }, + styles: { + showRegion: + existingSlots?.AddressSlot?.[0]?.props?.styles?.showRegion ?? + true, + showCountry: + existingSlots?.AddressSlot?.[0]?.props?.styles?.showCountry ?? + true, + showGetDirectionsLink: + existingSlots?.AddressSlot?.[0]?.props?.styles + ?.showGetDirectionsLink ?? false, + ctaVariant: + existingSlots?.AddressSlot?.[0]?.props?.styles?.ctaVariant ?? + "link", + }, + parentData: { + field: "profile.address", + }, + }, + }, + ], + PhoneSlot: [ + { + type: "PhoneSlot", + props: { + ...(id && { id: `${id}-phone` }), + data: { + number: { + constantValue: "", + field: "mainPhone", + }, + label: { + constantValue: "", + hasLocalizedValue: "true", + field: "", + }, + }, + styles: { + phoneFormat: + existingSlots?.PhoneSlot?.[0]?.props?.styles?.phoneFormat ?? + "domestic", + includePhoneHyperlink: + existingSlots?.PhoneSlot?.[0]?.props?.styles + ?.includePhoneHyperlink ?? true, + includeIcon: + existingSlots?.PhoneSlot?.[0]?.props?.styles?.includeIcon ?? + false, + }, + parentData: { + field: "profile.mainPhone", + }, + }, + }, + ], + HoursSlot: [ + { + type: "HoursStatusSlot", + props: { + ...(id && { id: `${id}-hours` }), + data: { + hours: { + constantValue: {}, + field: "hours", + }, + }, + styles: { + dayOfWeekFormat: + existingSlots?.HoursSlot?.[0]?.props?.styles?.dayOfWeekFormat ?? + "long", + showDayNames: + existingSlots?.HoursSlot?.[0]?.props?.styles?.showDayNames ?? + true, + showCurrentStatus: + existingSlots?.HoursSlot?.[0]?.props?.styles + ?.showCurrentStatus ?? true, + className: + existingSlots?.HoursSlot?.[0]?.props?.styles?.className ?? + "mb-2 font-semibold font-body-fontFamily text-body-fontSize h-full", + }, + parentData: { + field: "profile.hours", + }, + }, + }, + ], + }, + }, +}); + +const isEntityFieldValue = ( + value: unknown +): value is { + field?: string; + constantValue?: unknown; + constantValueEnabled?: boolean; +} => + typeof value === "object" && + value !== null && + ("field" in value || "constantValue" in value); + +const isDefaultDirectoryCardTitle = (value: unknown): boolean => { + if (isEntityFieldValue(value)) { + return isDefaultDirectoryCardTitle(value.constantValue); + } + + return ( + typeof value === "object" && + value !== null && + "defaultValue" in value && + (value as { defaultValue?: unknown }).defaultValue === + defaultCardTitle.defaultValue + ); +}; + +const toHeadingTextField = (value: unknown) => + isEntityFieldValue(value) + ? { + field: value.field ?? "", + constantValueEnabled: value.constantValueEnabled ?? false, + constantValue: value.constantValue, + } + : { + field: "", + constantValueEnabled: true, + constantValue: value ?? defaultCardTitle, + }; + +const toDirectoryGridTitleMapping = (value: unknown) => { + if ( + isEntityFieldValue(value) && + !value.constantValueEnabled && + typeof value.field === "string" && + value.field.length > 0 + ) { + return { + field: value.field, + constantValueEnabled: false, + constantValue: value.constantValue, + }; + } + + if (isDefaultDirectoryCardTitle(value)) { + return { + field: "name", + constantValueEnabled: false, + constantValue: undefined, + }; + } + + const headingTextField = toHeadingTextField(value); + return { + field: "", + constantValueEnabled: true, + constantValue: headingTextField.constantValue ?? defaultCardTitle, + }; +}; + +const getLegacyCardTitle = (cardProps: LegacyDirectoryCardProps = {}) => { + const headingSlot = cardProps.slots?.HeadingSlot?.[0] as + | { + props?: { + data?: { text?: unknown }; + parentData?: { field?: string }; + }; + } + | undefined; + const savedHeadingText = + cardProps.data?.cardTitle ?? headingSlot?.props?.data?.text; + + if ( + savedHeadingText !== undefined && + savedHeadingText !== "" && + (!isEntityFieldValue(savedHeadingText) || + savedHeadingText.field !== "" || + (savedHeadingText.constantValue !== undefined && + savedHeadingText.constantValue !== "" && + savedHeadingText.constantValue !== null)) + ) { + return savedHeadingText; + } + + if (headingSlot?.props?.parentData?.field === "profile.name") { + return defaultCardTitle; + } + + return savedHeadingText; +}; + +const normalizeDirectoryCards = (cards: unknown) => { + if (!Array.isArray(cards)) { + return []; + } + + return cards.map((card, index) => { + if (!card || typeof card !== "object") { + return card; + } + + const cardProps = ((card as { props?: LegacyDirectoryCardProps }).props ?? + {}) as LegacyDirectoryCardProps; + const headingText = getLegacyCardTitle(cardProps ?? {}); + const normalizedCard = defaultDirectoryCardSlotData( + cardProps?.id ?? "", + cardProps?.index ?? index, + cardProps?.styles, + cardProps?.slots + ); + const headingSlot = normalizedCard.props.slots.HeadingSlot[0]; + + headingSlot.props = { + ...headingSlot.props, + data: { + ...headingSlot.props.data, + text: toHeadingTextField(headingText), + }, + }; + + Object.values(normalizedCard.props.slots).forEach((slotArray) => { + slotArray.forEach((slotChild) => { + const slotChildProps = slotChild.props as + | { parentData?: unknown } + | undefined; + + if (!slotChildProps || !("parentData" in slotChildProps)) { + return; + } + + delete slotChildProps.parentData; + }); + }); + + return normalizedCard; + }); +}; + +export const directoryCardTitleField: Migration = { + DirectoryGrid: { + action: "updated", + propTransformation: (props) => { + const firstLegacyCardProps = + Array.isArray(props.slots?.CardSlot) && + props.slots.CardSlot[0] && + typeof props.slots.CardSlot[0] === "object" + ? (((props.slots.CardSlot[0] as { props?: LegacyDirectoryCardProps }) + .props ?? {}) as LegacyDirectoryCardProps) + : {}; + const cards = normalizeDirectoryCards(props.slots?.CardSlot); + const firstHeadingText = getLegacyCardTitle(firstLegacyCardProps); + + return { + ...props, + data: { + field: "dm_directoryChildren", + constantValueEnabled: false, + constantValue: [], + mappings: { + cardTitle: toDirectoryGridTitleMapping(firstHeadingText), + }, + }, + slots: { + ...props.slots, + CardSlot: cards, + }, + manualSlots: { + CardSlot: cards, + }, + }; + }, + }, +}; diff --git a/packages/visual-editor/src/components/migrations/migrationRegistry.ts b/packages/visual-editor/src/components/migrations/migrationRegistry.ts index d44da9307b..0e1821f3c3 100644 --- a/packages/visual-editor/src/components/migrations/migrationRegistry.ts +++ b/packages/visual-editor/src/components/migrations/migrationRegistry.ts @@ -75,6 +75,7 @@ import { mainContentWrapperMigration } from "./0073_main_content_wrapper.ts"; import { flattenLocatorResultCardSingleSelectFields } from "./0074_flatten_locator_result_card_single_select_fields.ts"; import { normalizeFooterLogoImageMigration } from "./0075_normalize_footer_logo_image.ts"; import { slotMappedCardsMigration } from "./0076_slot_mapped_cards.ts"; +import { directoryCardTitleField } from "./0077_directory_card_title_field.ts"; // To add a migration: // Create a new file in this directory that exports a Migration @@ -158,4 +159,5 @@ export const migrationRegistry: MigrationRegistry = [ flattenLocatorResultCardSingleSelectFields, normalizeFooterLogoImageMigration, slotMappedCardsMigration, + directoryCardTitleField, ]; diff --git a/packages/visual-editor/src/components/testing/screenshots/Directory/[desktop] version 77 - City with custom card titles.png b/packages/visual-editor/src/components/testing/screenshots/Directory/[desktop] version 77 - City with custom card titles.png new file mode 100644 index 0000000000..1d09400064 Binary files /dev/null and b/packages/visual-editor/src/components/testing/screenshots/Directory/[desktop] version 77 - City with custom card titles.png differ diff --git a/packages/visual-editor/src/components/testing/screenshots/Directory/[mobile] version 77 - City with custom card titles.png b/packages/visual-editor/src/components/testing/screenshots/Directory/[mobile] version 77 - City with custom card titles.png new file mode 100644 index 0000000000..85a6db7993 Binary files /dev/null and b/packages/visual-editor/src/components/testing/screenshots/Directory/[mobile] version 77 - City with custom card titles.png differ diff --git a/packages/visual-editor/src/components/testing/screenshots/Directory/[tablet] version 77 - City with custom card titles.png b/packages/visual-editor/src/components/testing/screenshots/Directory/[tablet] version 77 - City with custom card titles.png new file mode 100644 index 0000000000..88b28e49ed Binary files /dev/null and b/packages/visual-editor/src/components/testing/screenshots/Directory/[tablet] version 77 - City with custom card titles.png differ diff --git a/packages/visual-editor/src/components/testing/screenshots/Locator/[mobile] latest version default props.png b/packages/visual-editor/src/components/testing/screenshots/Locator/[mobile] latest version default props.png index 144bfb3093..552a6108a5 100644 Binary files a/packages/visual-editor/src/components/testing/screenshots/Locator/[mobile] latest version default props.png and b/packages/visual-editor/src/components/testing/screenshots/Locator/[mobile] latest version default props.png differ diff --git a/packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[desktop] version 59 with showSectionHeading false.png b/packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[desktop] version 59 with showSectionHeading false.png index 78479fbdf2..b9bf47805f 100644 Binary files a/packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[desktop] version 59 with showSectionHeading false.png and b/packages/visual-editor/src/components/testing/screenshots/PhotoGallerySection/[desktop] version 59 with showSectionHeading false.png differ diff --git a/packages/visual-editor/src/editor/EmbeddedFieldStringInput.test.tsx b/packages/visual-editor/src/editor/EmbeddedFieldStringInput.test.tsx index 9cc55875cc..e39a223f9b 100644 --- a/packages/visual-editor/src/editor/EmbeddedFieldStringInput.test.tsx +++ b/packages/visual-editor/src/editor/EmbeddedFieldStringInput.test.tsx @@ -1,11 +1,12 @@ import React from "react"; import { fireEvent, render, screen } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import { EntityFieldsContext } from "../hooks/useEntityFields.tsx"; import { TemplatePropsContext } from "../hooks/useDocument.tsx"; import { TemplateMetadataContext } from "../internal/hooks/useMessageReceivers.ts"; import { generateTemplateMetadata } from "../internal/types/templateMetadata.ts"; import { EmbeddedFieldStringInputFromEntity } from "./EmbeddedFieldStringInput.tsx"; +import { toast } from "sonner"; const puckState = { appState: { @@ -29,7 +30,16 @@ vi.mock("@puckeditor/core", async () => { }; }); +vi.mock("sonner", () => ({ + toast: { + warning: vi.fn(), + }, +})); + describe("EmbeddedFieldStringInput", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); it("includes linked entity fields in the embedded field selector", () => { render( @@ -107,4 +117,246 @@ describe("EmbeddedFieldStringInput", () => { expect(screen.getByText("Linked Location > Address > City")).toBeDefined(); }); + + it("scopes mapped-item embedded fields to one entity group and includes nested linked descendants", () => { + render( + + + Name", + "c_articles.description": "Articles > Description", + "c_articles.c_linkedLocation": "Articles > Linked Location", + "c_articles.c_linkedLocation.name": + "Articles > Linked Location > Name", + }, + }} + > + undefined} + showFieldSelector={true} + sourceField="c_articles" + value="" + /> + + + + ); + + fireEvent.click(screen.getByLabelText("Add entity field")); + + expect(screen.queryByText("Linked Entity Fields")).toBeNull(); + expect(screen.getByText("Name")).toBeDefined(); + expect(screen.getByText("Description")).toBeDefined(); + expect(screen.getByText("Linked Location > Name")).toBeDefined(); + }); + + it("warns when selecting an embedded field through multiple linked entities", () => { + render( + + + Name", + }, + }} + > + undefined} + showFieldSelector={true} + value="" + /> + + + + ); + + fireEvent.click(screen.getByLabelText("Add entity field")); + fireEvent.click(screen.getByText("Linked Location > Name")); + + expect(toast.warning).toHaveBeenCalledWith( + "Linked Location contains multiple linked entities. Using the first one for Linked Location > Name." + ); + }); + + it("does not warn for scoped embedded fields mapped across multiple cards", () => { + render( + + + Linked Location", + "c_articles.c_linkedLocation.name": + "Articles > Linked Location > Name", + }, + }} + > + undefined} + showFieldSelector={true} + sourceField="c_articles" + value="" + /> + + + + ); + + fireEvent.click(screen.getByLabelText("Add entity field")); + fireEvent.click(screen.getByText("Linked Location > Name")); + + expect(toast.warning).not.toHaveBeenCalled(); + }); }); diff --git a/packages/visual-editor/src/editor/EmbeddedFieldStringInput.tsx b/packages/visual-editor/src/editor/EmbeddedFieldStringInput.tsx index 0a282ceb63..c0e245a0f6 100644 --- a/packages/visual-editor/src/editor/EmbeddedFieldStringInput.tsx +++ b/packages/visual-editor/src/editor/EmbeddedFieldStringInput.tsx @@ -33,6 +33,7 @@ import { type EntityFieldOptionGroup, } from "./entityFieldOptionGroups.ts"; import { TemplateMetadataContext } from "../internal/hooks/useMessageReceivers.ts"; +import { warnOnMultiValueLinkedEntityTraversal } from "../utils/linkedEntityWarningUtils.ts"; export type EmbeddedStringOption = { label: string; @@ -149,6 +150,14 @@ export const EmbeddedFieldStringInputFromEntity = < showFieldSelector={showFieldSelector} useOptionValueSublabel={false} sourceField={sourceField} + onFieldSelect={(fieldPath, resolutionFieldPath, resolutionDocument) => + warnOnMultiValueLinkedEntityTraversal( + resolutionDocument ?? streamDocument, + resolutionFieldPath ?? fieldPath, + entityFields, + fieldPath + ) + } /> ); }; @@ -170,6 +179,7 @@ export const EmbeddedFieldStringInputFromOptions = ({ showFieldSelector, useOptionValueSublabel = false, sourceField, + onFieldSelect, }: { value: string; onChange: (value: string) => void; @@ -177,6 +187,11 @@ export const EmbeddedFieldStringInputFromOptions = ({ showFieldSelector: boolean; useOptionValueSublabel?: boolean; sourceField?: string; + onFieldSelect?: ( + fieldPath: string, + resolutionFieldPath?: string, + resolutionDocument?: Record + ) => void; }) => { const [open, setOpen] = React.useState(false); const [cursorPosition, setCursorPosition] = React.useState( @@ -184,6 +199,7 @@ export const EmbeddedFieldStringInputFromOptions = ({ ); const [inputValue, setInputValue] = React.useState(value); const [prevPropValue, setPrevPropValue] = React.useState(value); + const streamDocument = useDocument(); if (value !== prevPropValue) { setPrevPropValue(value); @@ -225,6 +241,13 @@ export const EmbeddedFieldStringInputFromOptions = ({ const handleFieldSelect = (fieldName: string) => { setOpen(false); if (!fieldName) return; + const resolutionPath = sourceField + ? `${sourceField}.${fieldName}` + : fieldName; + const resolutionDocument = sourceField + ? (getSubDocument(streamDocument, sourceField) ?? streamDocument) + : undefined; + onFieldSelect?.(resolutionPath, resolutionPath, resolutionDocument); const textToInsert = `[[${fieldName}]]`; let insertionPoint = cursorPosition ?? inputValue.length; diff --git a/packages/visual-editor/src/editor/TranslatableRichTextField.test.tsx b/packages/visual-editor/src/editor/TranslatableRichTextField.test.tsx new file mode 100644 index 0000000000..9ca8002c94 --- /dev/null +++ b/packages/visual-editor/src/editor/TranslatableRichTextField.test.tsx @@ -0,0 +1,80 @@ +import React from "react"; +import { fireEvent, render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { TemplatePropsContext } from "../hooks/useDocument.tsx"; +import { TranslatableRichTextField } from "./TranslatableRichTextField.tsx"; +import { RepeatedSourceFieldContext } from "../fields/repeatedSourceFieldContext.ts"; +import { msg } from "../utils/i18n/platform.ts"; + +const { sendToParentMock } = vi.hoisted(() => ({ + sendToParentMock: vi.fn(), +})); + +vi.mock("react-i18next", async (importOriginal) => { + const actual = await importOriginal(); + + return { + ...actual, + useTranslation: () => ({ + i18n: { language: "en" }, + }), + }; +}); + +vi.mock("../internal/hooks/useMessage.ts", () => ({ + TARGET_ORIGINS: [], + useReceiveMessage: vi.fn(), + useSendMessageToParent: () => ({ + sendToParent: sendToParentMock, + }), +})); + +vi.mock("../utils/isFakeStarterLocalDev.ts", () => ({ + isFakeStarterLocalDev: () => false, +})); + +vi.mock("../internal/utils/shouldUseStandaloneLocalPrompt.ts", () => ({ + shouldUseStandaloneLocalPrompt: () => false, +})); + +const RichTextFieldRenderer = ({ + value, +}: { + value?: { defaultValue?: { html?: string; json?: string } }; +}) => { + const field = TranslatableRichTextField(msg("body", "Body")); + + return field.render({ + field, + id: "rich-text-field", + name: "body", + onChange: vi.fn(), + readOnly: false, + value, + } as Parameters[0]); +}; + +describe("TranslatableRichTextField", () => { + it("passes the repeated source field to Storm when opening the editor", () => { + render( + + + + + + ); + + fireEvent.click(screen.getByRole("button")); + + expect(sendToParentMock).toHaveBeenCalledWith({ + payload: { + type: "RichTextValue", + value: undefined, + id: expect.stringMatching(/^RichText-/), + fieldName: "Body (en)", + locale: "en", + sourceField: "c_eventsSection.events", + }, + }); + }); +}); diff --git a/packages/visual-editor/src/editor/TranslatableRichTextField.tsx b/packages/visual-editor/src/editor/TranslatableRichTextField.tsx index 25c53709df..9ffeda8466 100644 --- a/packages/visual-editor/src/editor/TranslatableRichTextField.tsx +++ b/packages/visual-editor/src/editor/TranslatableRichTextField.tsx @@ -10,6 +10,7 @@ import { } from "../internal/hooks/useMessage.ts"; import { shouldUseStandaloneLocalPrompt } from "../internal/utils/shouldUseStandaloneLocalPrompt.ts"; import { useTranslation } from "react-i18next"; +import { RepeatedSourceFieldContext } from "../fields/repeatedSourceFieldContext.ts"; let pendingRichTextSession: | { messageId: string; apply: (payload: any) => void } @@ -29,6 +30,7 @@ export function TranslatableRichTextField< const locale = i18n.language; const resolvedValue = value && resolveComponentData(value, locale); const fieldLabel = label ? `${pt(label)} (${locale})` : ""; + const sourceField = React.useContext(RepeatedSourceFieldContext); const { sendToParent: openConstantValueEditor } = useSendMessageToParent( "constantValueEditorOpened", @@ -80,6 +82,7 @@ export function TranslatableRichTextField< id: messageId, fieldName: fieldLabel, locale: locale, + sourceField: sourceField, }, }); }; diff --git a/packages/visual-editor/src/editor/yextEntityFieldUtils.test.ts b/packages/visual-editor/src/editor/yextEntityFieldUtils.test.ts index 28f5223a93..c17822179f 100644 --- a/packages/visual-editor/src/editor/yextEntityFieldUtils.test.ts +++ b/packages/visual-editor/src/editor/yextEntityFieldUtils.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from "vitest"; -import { getFieldsForSelector } from "./yextEntityFieldUtils.ts"; +import { + getEntityFieldDisplayName, + getFieldsForSelector, +} from "./yextEntityFieldUtils.ts"; describe("getFieldsForSelector", () => { it("allows one descendant to satisfy multiple compatible mapping requirements", () => { @@ -124,3 +127,14 @@ describe("getFieldsForSelector", () => { ); }); }); + +describe("getEntityFieldDisplayName", () => { + it("falls back to the first field-path segment when the schema path is unknown", () => { + expect( + getEntityFieldDisplayName("c_linkedEntity.unknownField", { + fields: [], + displayNames: {}, + }) + ).toBe("c_linkedEntity"); + }); +}); diff --git a/packages/visual-editor/src/editor/yextEntityFieldUtils.ts b/packages/visual-editor/src/editor/yextEntityFieldUtils.ts index edcd97de93..0a0a89f0b9 100644 --- a/packages/visual-editor/src/editor/yextEntityFieldUtils.ts +++ b/packages/visual-editor/src/editor/yextEntityFieldUtils.ts @@ -5,10 +5,7 @@ import { import { StreamFields, YextSchemaField } from "../types/entityFields.ts"; import { resolveField } from "../utils/resolveYextEntityField.ts"; import { type StreamDocument } from "../utils/types/StreamDocument.ts"; -import { - getTopLevelLinkedEntitySourceFields, - isLinkedEntityDefinition, -} from "../utils/linkedEntityFieldUtils.ts"; +import { getTopLevelLinkedEntitySourceFields } from "../utils/linkedEntityFieldUtils.ts"; import { getListSourceRootFields, type MappedSourceFieldFilter, @@ -127,7 +124,7 @@ export const getEntityFieldDisplayName = ( if (!matchingField) { return displayNameSegments.length ? displayNameSegments.join(DISPLAY_NAME_SEPARATOR) - : undefined; + : fieldPathSegments[0]; } const mappedDisplayName = getMappedDisplayName(currentPath, entityFields); @@ -271,35 +268,8 @@ const getSubdocumentStreamFields = ( }; /** - * Detects whether a scoped descendant path passes through another linked - * entity reference, which would otherwise leak unrelated nested fields into the - * current picker scope. - */ -const hasNestedLinkedEntityAncestor = ( - scopedStreamFields: StreamFields, - relativeFieldPath: string -): boolean => { - const pathSegments = relativeFieldPath.split("."); - let currentFields = scopedStreamFields.fields; - - for (const segment of pathSegments.slice(0, -1)) { - const matchingField = currentFields.find((field) => field.name === segment); - if (!matchingField) { - return false; - } - if (isLinkedEntityDefinition(matchingField.definition)) { - return true; - } - currentFields = matchingField.children?.fields ?? []; - } - - return false; -}; - -/** - * Returns selector fields relative to a selected source item, removing nested - * linked-entity descendants and trimming the repeated root label from each - * option. + * Returns selector fields relative to a selected source item and trims the + * repeated root label from each option. */ const getScopedFieldsForSelector = ( entityFields: StreamFields | null, @@ -319,28 +289,23 @@ const getScopedFieldsForSelector = ( return sortFields( dedupeFieldsByName( - getFilteredEntityFields(scopedStreamFields, filter) - .filter( - (field) => - !hasNestedLinkedEntityAncestor(scopedStreamFields, field.name) - ) - .map((field) => { - const displayName = - getEntityFieldDisplayName( - `${sourceField}.${field.name}`, - entityFields - ) ?? - field.displayName ?? - field.name; - - return { - ...field, - displayName: - rootPrefix && displayName.startsWith(rootPrefix) - ? displayName.slice(rootPrefix.length) - : displayName, - }; - }) + getFilteredEntityFields(scopedStreamFields, filter).map((field) => { + const displayName = + getEntityFieldDisplayName( + `${sourceField}.${field.name}`, + entityFields + ) ?? + field.displayName ?? + field.name; + + return { + ...field, + displayName: + rootPrefix && displayName.startsWith(rootPrefix) + ? displayName.slice(rootPrefix.length) + : displayName, + }; + }) ) ); }; diff --git a/packages/visual-editor/src/fields/EntityFieldSelectorField.test.tsx b/packages/visual-editor/src/fields/EntityFieldSelectorField.test.tsx index 3a3d2e8bef..baf8fb8c70 100644 --- a/packages/visual-editor/src/fields/EntityFieldSelectorField.test.tsx +++ b/packages/visual-editor/src/fields/EntityFieldSelectorField.test.tsx @@ -664,6 +664,184 @@ describe("EntityFieldSelectorField", () => { expect(screen.getByText("Products Fields")).toBeDefined(); }); + it("uses the repeated source scope for mapping constant-mode embedded pickers", () => { + renderRepeatedEntityField({ + value: { + field: "c_articles", + constantValueEnabled: false, + constantValue: [], + mappings: { + title: { + field: "", + constantValueEnabled: true, + constantValue: { defaultValue: "" }, + }, + }, + }, + entityFields: { + ...defaultEntityFields, + fields: [ + { + name: "c_articles", + definition: { + name: "c_articles", + typeName: "c_articles", + isList: true, + type: {}, + }, + children: { + fields: [ + { + name: "name", + definition: { + name: "name", + typeName: "type.string", + type: {}, + }, + }, + { + name: "c_linkedLocation", + displayName: "Linked Location", + definition: { + name: "c_linkedLocation", + typeRegistryId: "type.entity_reference", + type: { + documentType: "DOCUMENT_TYPE_ENTITY", + }, + }, + children: { + fields: [ + { + name: "name", + definition: { + name: "name", + typeName: "type.string", + type: {}, + }, + }, + ], + }, + }, + ], + }, + }, + ...defaultEntityFields.fields, + ], + displayNames: { + ...defaultEntityFields.displayNames, + c_articles: "Articles", + "c_articles.name": "Articles > Name", + "c_articles.c_linkedLocation": "Articles > Linked Location", + "c_articles.c_linkedLocation.name": + "Articles > Linked Location > Name", + }, + }, + }); + + fireEvent.click(screen.getByLabelText("Add entity field")); + + expect(screen.getByText("Name")).toBeDefined(); + expect(screen.queryByText("Description")).toBeNull(); + expect(screen.queryByText("Location Fields")).toBeNull(); + expect(screen.queryByText("Linked Entity Fields")).toBeNull(); + expect(screen.getByText("Linked Location > Name")).toBeDefined(); + }); + + it("hides the repeated source selector when the source field is fixed", () => { + renderRepeatedEntityField({ + field: { + type: "entityField", + label: "Directory Children", + filter: { + itemSourceTypes: [["type.string"]], + }, + disableConstantValueToggle: true, + fixedRepeatedField: "dm_directoryChildren", + repeated: { + defaultItemValue: { + title: { + field: "", + constantValueEnabled: true, + constantValue: { defaultValue: "" }, + }, + }, + defaultMappings: { + title: { + field: "", + constantValueEnabled: false, + constantValue: { defaultValue: "" }, + }, + }, + manualItemFields: { + title: { + type: "entityField", + label: "Title", + filter: { types: ["type.string"] }, + }, + }, + mappingFields: { + title: { + type: "entityField", + label: "Title", + filter: { types: ["type.string"] }, + }, + }, + }, + }, + value: { + field: "dm_directoryChildren", + constantValueEnabled: false, + constantValue: [], + mappings: { + title: { + field: "name", + constantValueEnabled: false, + constantValue: { defaultValue: "" }, + }, + }, + }, + entityFields: { + ...defaultEntityFields, + fields: [ + { + name: "dm_directoryChildren", + definition: { + name: "dm_directoryChildren", + typeName: "dm_directoryChildren", + isList: true, + type: {}, + }, + children: { + fields: [ + { + name: "name", + definition: { + name: "name", + typeName: "type.string", + type: {}, + }, + }, + ], + }, + }, + ...defaultEntityFields.fields, + ], + displayNames: { + ...defaultEntityFields.displayNames, + dm_directoryChildren: "Directory Children", + "dm_directoryChildren.name": "Directory Children > Name", + }, + }, + }); + + expect(screen.getAllByRole("switch")).toHaveLength(1); + expect(screen.getAllByRole("combobox")).toHaveLength(1); + + fireEvent.click(screen.getAllByRole("combobox")[0]); + + expect(screen.getByText("Directory Children Fields")).toBeDefined(); + expect(screen.queryByText("Location Fields")).toBeNull(); + }); it("clears stale repeated mappings when switching between linked sources", () => { const { onChange } = renderRepeatedEntityField({ value: { diff --git a/packages/visual-editor/src/fields/EntityFieldSelectorField.tsx b/packages/visual-editor/src/fields/EntityFieldSelectorField.tsx index 245bf460cb..22c4fb4ac8 100644 --- a/packages/visual-editor/src/fields/EntityFieldSelectorField.tsx +++ b/packages/visual-editor/src/fields/EntityFieldSelectorField.tsx @@ -41,6 +41,7 @@ import { returnConstantFieldConfig, supportsLocalizedConstantValue, } from "./entityFieldConstantConfig.ts"; +import { RepeatedSourceFieldContext } from "./repeatedSourceFieldContext.ts"; export { getConstantConfigFromType, returnConstantFieldConfig, @@ -74,15 +75,12 @@ export type EntityFieldSelectorField< disableConstantValueToggle?: boolean; disallowTranslation?: boolean; sourceFieldPath?: string; + fixedRepeatedField?: string; repeated?: RepeatedEntityFieldMetadata; }; type EntityFieldSelectorFieldProps = FieldProps; -const RepeatedEntitySourceFieldContext = React.createContext< - string | undefined ->(undefined); - const clearEntityFieldBindings = (value: unknown): unknown => { if ( value && @@ -138,7 +136,9 @@ const hasEntityFieldBindings = (value: unknown): boolean => { * * 1. Lets the user switch between linked-list mode and manual-item mode. * 2. Shows either the linked source selector or the inline manual item editor. - * 3. Clears stale linked mapping selections when the user switches between + * 3. Shares the active repeated source field with both KG pickers and + * constant-mode embedded pickers inside the mapping editor. + * 4. Clears stale linked mapping selections when the user switches between * linked parent sources while preserving any authored constant values. */ const RepeatedEntityFieldSelector = ({ @@ -152,7 +152,7 @@ const RepeatedEntityFieldSelector = ({ const translatedLabel = field.label ? pt(field.label) : ""; const constantValueEnabled = !!value?.constantValueEnabled; const baseValue: RepeatedEntityFieldValue> = { - field: value?.field ?? "", + field: field.fixedRepeatedField || value?.field || "", constantValueEnabled: value?.constantValueEnabled ?? true, constantValue: value?.constantValue ?? [repeated.defaultItemValue], mappings: value?.mappings ?? repeated.defaultMappings, @@ -222,6 +222,7 @@ const RepeatedEntityFieldSelector = ({ constantValueEnabled: nextConstantValueEnabled, }) } + disableConstantValue={field.disableConstantValueToggle} label={translatedLabel} /> {constantValueEnabled ? ( @@ -239,14 +240,16 @@ const RepeatedEntityFieldSelector = ({ ) : ( <> - - {!!baseValue.field && ( - + {!field.fixedRepeatedField && ( + + )} + {!!(field.fixedRepeatedField || baseValue.field) && ( +
-
+ )} )} @@ -419,6 +422,7 @@ export const ConstantValueInput = >({ onChange, value, disallowTranslation, + sourceField, }: InputProps) => { const constantFieldConfig = returnConstantFieldConfig( filter.types, @@ -430,6 +434,22 @@ export const ConstantValueInput = >({ return; } + // Reuse the active repeated source for nested constant editors so embedded + // field pickers stay scoped to the current mapped item unless the config + // already authored an explicit sourceField. + const sourceFieldFromContext = React.useContext(RepeatedSourceFieldContext); + const resolvedSourceField = + ("sourceField" in constantFieldConfig + ? constantFieldConfig.sourceField + : undefined) ?? + sourceField ?? + sourceFieldFromContext; + const scopedConstantField = resolvedSourceField + ? ({ + ...constantFieldConfig, + sourceField: resolvedSourceField, + } as YextFieldDefinition) + : constantFieldConfig; const fieldEditor = ( @@ -442,7 +462,7 @@ export const ConstantValueInput = >({ ) } value={value?.constantValue} - field={constantFieldConfig} + field={scopedConstantField} /> ); @@ -484,9 +504,7 @@ export const EntityFieldInput = >({ const entityFields = useEntityFields(); const streamDocument = useDocument(); const templateMetadata = React.useContext(TemplateMetadataContext); - const sourceFieldFromContext = React.useContext( - RepeatedEntitySourceFieldContext - ); + const sourceFieldFromContext = React.useContext(RepeatedSourceFieldContext); const sourceFieldFromProps = useCurrentSourceField(sourceFieldPath); const sourceField = sourceFieldFromInputProps || @@ -611,7 +629,11 @@ export const EntityFieldInput = >({ return; } - warnOnMultiValueLinkedEntityTraversal(streamDocument, value.field); + warnOnMultiValueLinkedEntityTraversal( + streamDocument, + value.field, + entityFields + ); }, [ filter.includeListsOnly, entityFields, diff --git a/packages/visual-editor/src/fields/TranslatableStringField.test.tsx b/packages/visual-editor/src/fields/TranslatableStringField.test.tsx index 5616d09854..0626e43ddb 100644 --- a/packages/visual-editor/src/fields/TranslatableStringField.test.tsx +++ b/packages/visual-editor/src/fields/TranslatableStringField.test.tsx @@ -22,12 +22,16 @@ vi.mock("../editor/EmbeddedFieldStringInput.tsx", () => ({ filter, onChange, showFieldSelector, + sourceField, + sourceFieldPath, value, }: any) => (