From a73451de451e9745b9790537bfe6d43717cce603 Mon Sep 17 00:00:00 2001 From: Chris Helgert Date: Fri, 27 Oct 2023 11:01:30 +0200 Subject: [PATCH] refactor(nested-updates): move depth handling around and decrease depth for richtext --- packages/live-preview-sdk/src/constants.ts | 1 + .../src/graphql/__tests__/entries.test.ts | 3 +- .../live-preview-sdk/src/graphql/entries.ts | 89 ++++++++++--------- .../src/helpers/resolveReference.ts | 6 +- packages/live-preview-sdk/src/liveUpdates.ts | 8 +- .../live-preview-sdk/src/rest/entities.ts | 57 +++++++----- packages/live-preview-sdk/src/types.ts | 6 +- 7 files changed, 97 insertions(+), 73 deletions(-) diff --git a/packages/live-preview-sdk/src/constants.ts b/packages/live-preview-sdk/src/constants.ts index 70657b26..d4df6514 100644 --- a/packages/live-preview-sdk/src/constants.ts +++ b/packages/live-preview-sdk/src/constants.ts @@ -9,6 +9,7 @@ export const TOOLTIP_HEIGHT = 32; export const TOOLTIP_PADDING_LEFT = 5; export const MAX_DEPTH = 10; +export const MAX_RTE_DEPTH = 2; export const LIVE_PREVIEW_EDITOR_SOURCE = 'live-preview-editor' as const; export const LIVE_PREVIEW_SDK_SOURCE = 'live-preview-sdk' as const; diff --git a/packages/live-preview-sdk/src/graphql/__tests__/entries.test.ts b/packages/live-preview-sdk/src/graphql/__tests__/entries.test.ts index f3a89db4..ad6e3902 100644 --- a/packages/live-preview-sdk/src/graphql/__tests__/entries.test.ts +++ b/packages/live-preview-sdk/src/graphql/__tests__/entries.test.ts @@ -8,6 +8,7 @@ import type { SysProps, Entity, ContentType, GetStore } from '../../types'; import { updateEntry } from '../entries'; import defaultContentTypeJSON from './fixtures/contentType.json'; import entry from './fixtures/entry.json'; +import { MAX_DEPTH } from '../../constants'; const EN = 'en-US'; @@ -58,7 +59,7 @@ describe('Update GraphQL Entry', () => { updateFromEntryEditor: update, locale, getStore, - depth: 0, + maxDepth: MAX_DEPTH, }); }; diff --git a/packages/live-preview-sdk/src/graphql/entries.ts b/packages/live-preview-sdk/src/graphql/entries.ts index 39f30767..c242f0ab 100644 --- a/packages/live-preview-sdk/src/graphql/entries.ts +++ b/packages/live-preview-sdk/src/graphql/entries.ts @@ -18,7 +18,7 @@ import { import { updateAsset } from './assets'; import { isRelevantField, updateAliasedInformation } from './queryUtils'; import { buildCollectionName, generateTypeName } from './utils'; -import { MAX_DEPTH } from '../constants'; +import { MAX_RTE_DEPTH } from '../constants'; /** * Updates GraphQL response data based on CMA entry object @@ -33,6 +33,7 @@ export async function updateEntry({ contentType, dataFromPreviewApp, updateFromEntryEditor, + maxDepth, ...props }: UpdateEntryProps): Promise { if (dataFromPreviewApp.sys.id !== updateFromEntryEditor.sys.id) { @@ -56,6 +57,7 @@ export async function updateEntry({ dataFromPreviewApp: copyOfDataFromPreviewApp, updateFromEntryEditor, name, + maxDepth: MAX_RTE_DEPTH, }); } else if (field.type === 'Link') { await updateSingleRefField({ @@ -63,6 +65,7 @@ export async function updateEntry({ dataFromPreviewApp: copyOfDataFromPreviewApp, updateFromEntryEditor, name, + maxDepth: maxDepth - 1, }); } else if (field.type === 'Array' && field.items?.type === 'Link') { await updateMultiRefField({ @@ -70,6 +73,7 @@ export async function updateEntry({ dataFromPreviewApp: copyOfDataFromPreviewApp, updateFromEntryEditor, name, + maxDepth: maxDepth - 1, }); } } @@ -100,7 +104,7 @@ async function processNode({ locale: string; getStore: GetStore; gqlParams?: GraphQLParams; - depth: number; + maxDepth: number; }) { // Check if the node is an embedded entity if (SUPPORTED_RICHTEXT_EMBEDS.includes(node.nodeType)) { @@ -173,7 +177,7 @@ async function processRichTextField({ locale: string; getStore: GetStore; gqlParams?: GraphQLParams; - depth: number; + maxDepth: number; }): Promise<{ entries: RichTextLink; assets: RichTextLink }> { const entries: RichTextLink = { block: [], inline: [], hyperlink: [] }; const assets: RichTextLink = { block: [], inline: [], hyperlink: [] }; @@ -202,7 +206,7 @@ async function updateRichTextField({ locale, gqlParams, getStore, - depth, + maxDepth, }: UpdateFieldProps) { if (!dataFromPreviewApp[name]) { dataFromPreviewApp[name] = {}; @@ -218,7 +222,7 @@ async function updateRichTextField({ locale, getStore, gqlParams, - depth, + maxDepth, }); } @@ -247,8 +251,24 @@ async function updateReferenceAssetField({ ); } -function isInDepthLimit(depth: number, gqlParams?: GraphQLParams): boolean { - return !!gqlParams || depth < MAX_DEPTH; +function isInDepthLimit(entityId: string, maxDepth: number, gqlParams?: GraphQLParams): boolean { + if (gqlParams) { + return true; + } + + if (maxDepth > 0) { + return true; + } + + debug.log( + 'Max update depth is reached, please provide the GraphQL query if you need deeper nested information.', + { entityId } + ); + return false; +} + +function getRichTextDepth(maxDepth: number) { + return Math.min(maxDepth - 1, MAX_RTE_DEPTH); } async function updateReferenceEntryField({ @@ -257,7 +277,7 @@ async function updateReferenceEntryField({ locale, gqlParams, getStore, - depth, + maxDepth, }: SetOptional, 'gqlParams'>) { const { reference, typeName } = await resolveReference({ referenceId: updatedReference.sys.id, @@ -287,12 +307,12 @@ async function updateReferenceEntryField({ richTextNode: value, locale, gqlParams, - depth, + maxDepth: getRichTextDepth(maxDepth), getStore, }); } - if ('sys' in value && isInDepthLimit(depth, gqlParams)) { + if ('sys' in value && isInDepthLimit(updatedReference.sys.id, maxDepth, gqlParams)) { // single reference merged[key] = value; await updateSingleRefField({ @@ -302,10 +322,14 @@ async function updateReferenceEntryField({ name: key, gqlParams, getStore, - depth: depth + 1, + maxDepth: maxDepth - 1, }); } - } else if (Array.isArray(value) && value[0]?.sys && isInDepthLimit(depth, gqlParams)) { + } else if ( + Array.isArray(value) && + value[0]?.sys && + isInDepthLimit(updatedReference.sys.id, maxDepth, gqlParams) + ) { // multi references const name = buildCollectionName(key); merged[name] = { items: value }; @@ -316,7 +340,7 @@ async function updateReferenceEntryField({ name: key, gqlParams, getStore, - depth: depth + 1, + maxDepth: maxDepth - 1, }); } else { // primitive fields @@ -330,10 +354,7 @@ async function updateReferenceEntryField({ async function updateReferenceField({ referenceFromPreviewApp, updatedReference, - locale, - gqlParams, - getStore, - depth, + ...props }: UpdateReferenceFieldProps) { if (!updatedReference) { return null; @@ -350,22 +371,16 @@ async function updateReferenceField({ if (isAsset(updatedReference)) { return updateReferenceAssetField({ + ...props, referenceFromPreviewApp, updatedReference, - locale, - gqlParams, - getStore, - depth, }); } return updateReferenceEntryField({ + ...props, referenceFromPreviewApp, updatedReference, - locale, - gqlParams, - getStore, - depth, }); } @@ -373,21 +388,17 @@ async function updateSingleRefField({ dataFromPreviewApp, updateFromEntryEditor, name, - locale, - gqlParams, - getStore, - depth, + maxDepth, + ...props }: UpdateFieldProps) { const updatedReference = updateFromEntryEditor?.fields?.[name] as Asset | Entry | undefined; dataFromPreviewApp[name] = await updateReferenceField({ + ...props, referenceFromPreviewApp: dataFromPreviewApp[name] as Entry & { __typename?: string; }, updatedReference, - locale, - gqlParams, - getStore, - depth, + maxDepth: maxDepth - 1, }); } @@ -395,10 +406,8 @@ async function updateMultiRefField({ dataFromPreviewApp, updateFromEntryEditor, name, - locale, - gqlParams, - getStore, - depth, + maxDepth, + ...props }: UpdateFieldProps) { const fieldName = buildCollectionName(name); @@ -410,14 +419,12 @@ async function updateMultiRefField({ )?.items?.find((item) => item.sys.id === updatedItem.sys.id); const result = await updateReferenceField({ + ...props, referenceFromPreviewApp: itemFromPreviewApp as unknown as Entry & { __typename?: string; }, updatedReference: updatedItem, - locale, - gqlParams, - getStore, - depth, + maxDepth: maxDepth - 1, }); return result; diff --git a/packages/live-preview-sdk/src/helpers/resolveReference.ts b/packages/live-preview-sdk/src/helpers/resolveReference.ts index 647949b5..d91c6a4d 100644 --- a/packages/live-preview-sdk/src/helpers/resolveReference.ts +++ b/packages/live-preview-sdk/src/helpers/resolveReference.ts @@ -30,8 +30,10 @@ export async function resolveReference({ locale: string; getStore: GetStore; }): Promise<{ reference: Entry | Asset; typeName: string }> { + const store = getStore(locale); + if (isAsset) { - const result = await getStore(locale).fetchAsset(referenceId); + const result = await store.fetchAsset(referenceId); if (!result) { throw new Error(`Unknown reference ${referenceId}`); } @@ -42,7 +44,7 @@ export async function resolveReference({ }; } - const result = await getStore(locale).fetchEntry(referenceId); + const result = await store.fetchEntry(referenceId); if (!result) { throw new Error(`Unknown reference ${referenceId}`); } diff --git a/packages/live-preview-sdk/src/liveUpdates.ts b/packages/live-preview-sdk/src/liveUpdates.ts index f933b508..a18fd1fb 100644 --- a/packages/live-preview-sdk/src/liveUpdates.ts +++ b/packages/live-preview-sdk/src/liveUpdates.ts @@ -26,6 +26,7 @@ import { GraphQLParams, } from './types'; import { EditorEntityStore } from '@contentful/visual-sdk'; +import { MAX_DEPTH } from './constants'; interface MergeEntityProps { dataFromPreviewApp: Entity; @@ -54,6 +55,7 @@ export class LiveUpdates { this.defaultLocale = locale; this.sendMessage = (method, data) => sendMessageToEditor(method, data, targetOrigin); this.storage = new StorageMap('live-updates', new Map()); + this.getStore = this.getStore.bind(this); window.addEventListener('beforeunload', () => this.clearStorage()); } @@ -97,8 +99,6 @@ export class LiveUpdates { data: Entity; updated: boolean; }> { - const depth = 0; - if ('__typename' in dataFromPreviewApp) { // GraphQL const data = await (dataFromPreviewApp.__typename === 'Asset' @@ -109,7 +109,7 @@ export class LiveUpdates { updateFromEntryEditor: updateFromEntryEditor as Entry, locale, gqlParams, - depth, + maxDepth: MAX_DEPTH, getStore: this.getStore, })); @@ -127,7 +127,7 @@ export class LiveUpdates { dataFromPreviewApp as Entry, updateFromEntryEditor as Entry, locale, - depth, + MAX_DEPTH, this.getStore ), updated: true, diff --git a/packages/live-preview-sdk/src/rest/entities.ts b/packages/live-preview-sdk/src/rest/entities.ts index 5ab054f4..939fbdfe 100644 --- a/packages/live-preview-sdk/src/rest/entities.ts +++ b/packages/live-preview-sdk/src/rest/entities.ts @@ -1,7 +1,7 @@ import { Asset, Entry } from 'contentful'; import type { WithResourceName } from 'contentful-management'; -import { MAX_DEPTH } from '../constants'; +import { MAX_RTE_DEPTH } from '../constants'; import { debug, clone, isPrimitiveField, resolveReference, updatePrimitiveField } from '../helpers'; import { SUPPORTED_RICHTEXT_EMBEDS, @@ -26,6 +26,14 @@ function getFieldName(contentType: ContentType, field: ContentType['fields'][num return field.apiName || field.name; } +function isInDepthLimit(maxDepth: number): boolean { + return maxDepth >= 0; +} + +function getRichTextDepth(maxDepth: number) { + return Math.min(maxDepth - 1, MAX_RTE_DEPTH); +} + /** * Update the reference from the entry editor with the information from the store. * If the information is not yet available, it send a message to the editor to retrieve it. @@ -34,7 +42,7 @@ async function updateRef( dataFromPreviewApp: Reference | undefined, updateFromEntryEditor: Reference, locale: string, - depth: number, + maxDepth: number, getStore: GetStore ): Promise { const { reference } = await resolveReference({ @@ -55,23 +63,23 @@ async function updateRef( const value = reference.fields[key as keyof typeof reference.fields]; // single ref fields - if (isEntityLink(value) && depth < MAX_DEPTH) { + if (isEntityLink(value) && isInDepthLimit(maxDepth)) { await updateSingleRefField( result, reference, locale, key as keyof Reference['fields'], - depth + 1, + maxDepth - 1, getStore ); // multi ref fields - } else if (Array.isArray(value) && isEntityLink(value[0]) && depth < MAX_DEPTH) { + } else if (Array.isArray(value) && isEntityLink(value[0]) && isInDepthLimit(maxDepth)) { await updateMultiRefField( result, reference, locale, key as keyof Reference['fields'], - depth + 1, + maxDepth - 1, getStore ); // rich text fields @@ -81,7 +89,7 @@ async function updateRef( reference as Entry, key, locale, - depth + 1, + getRichTextDepth(maxDepth), getStore ); // single and multi resource link fields @@ -108,7 +116,7 @@ async function updateMultiRefField( updateFromEntryEditor: Reference, locale: string, name: keyof Reference['fields'], - depth: number, + maxDepth: number, getStore: GetStore ) { if (!updateFromEntryEditor.fields?.[name]) { @@ -123,7 +131,7 @@ async function updateMultiRefField( (dataFromPreviewApp.fields[name] as Reference[])?.[index], updateFromEntryReference, locale, - depth + 1, + maxDepth - 1, getStore ) ) @@ -138,7 +146,7 @@ async function updateSingleRefField( updateFromEntryEditor: Reference, locale: string, name: keyof Reference['fields'], - depth: number, + maxDepth: number, getStore: GetStore ) { const matchUpdateFromEntryEditor = updateFromEntryEditor?.fields?.[name] as Reference | undefined; @@ -154,14 +162,19 @@ async function updateSingleRefField( dataFromPreviewApp.fields[name] as Reference | undefined, matchUpdateFromEntryEditor, locale, - depth + 1, + maxDepth - 1, getStore ); return dataFromPreviewApp; } -async function resolveRichTextLinks(node: any, locale: string, depth: number, getStore: GetStore) { +async function resolveRichTextLinks( + node: any, + locale: string, + maxDepth: number, + getStore: GetStore +) { if (SUPPORTED_RICHTEXT_EMBEDS.includes(node.nodeType)) { if (node.data && node.data.target && node.data.target.sys) { if (node.data.target.sys.linkType === 'Entry' || node.data.target.sys.linkType === 'Asset') { @@ -174,7 +187,7 @@ async function resolveRichTextLinks(node: any, locale: string, depth: number, ge undefined, updatedReference, locale, - depth + 1, + maxDepth - 1, getStore ); } @@ -183,7 +196,7 @@ async function resolveRichTextLinks(node: any, locale: string, depth: number, ge if (node.content) { for (const childNode of node.content) { - await resolveRichTextLinks(childNode, locale, depth + 1, getStore); + await resolveRichTextLinks(childNode, locale, maxDepth - 1, getStore); } } } @@ -193,7 +206,7 @@ async function updateRichTextField( updateFromEntryEditor: Entry, name: string, locale: string, - depth: number, + maxDepth: number, getStore: GetStore ) { const richText = updateFromEntryEditor.fields?.[name]; @@ -203,7 +216,7 @@ async function updateRichTextField( dataFromPreviewApp.fields[name] = richText; // Resolve the linked entries or assets within the rich text field for (const node of richText.content) { - await resolveRichTextLinks(node, locale, depth, getStore); + await resolveRichTextLinks(node, locale, maxDepth, getStore); } } } @@ -218,7 +231,7 @@ export async function updateEntity( dataFromPreviewApp: Entry, updateFromEntryEditor: Entry | Asset, locale: string, - depth: number, + maxDepth: number, getStore: GetStore ): Promise { if (dataFromPreviewApp.sys.id !== updateFromEntryEditor.sys.id) { @@ -234,22 +247,22 @@ export async function updateEntity( updateFromEntryEditor, name, }); - } else if (field.type === 'Link' && depth < MAX_DEPTH) { + } else if (field.type === 'Link' && isInDepthLimit(maxDepth)) { await updateSingleRefField( dataFromPreviewApp, updateFromEntryEditor, locale, name as keyof Reference['fields'], - depth + 1, + maxDepth - 1, getStore ); - } else if (field.type === 'Array' && field.items?.type === 'Link' && depth < MAX_DEPTH) { + } else if (field.type === 'Array' && field.items?.type === 'Link' && isInDepthLimit(maxDepth)) { await updateMultiRefField( dataFromPreviewApp, updateFromEntryEditor, locale, name as keyof Reference['fields'], - depth + 1, + maxDepth - 1, getStore ); } else if (field.type === 'RichText') { @@ -258,7 +271,7 @@ export async function updateEntity( updateFromEntryEditor as Entry, name, locale, - depth, + MAX_RTE_DEPTH, getStore ); } else if (field.type === 'ResourceLink') { diff --git a/packages/live-preview-sdk/src/types.ts b/packages/live-preview-sdk/src/types.ts index b0bc5052..4b4471e2 100644 --- a/packages/live-preview-sdk/src/types.ts +++ b/packages/live-preview-sdk/src/types.ts @@ -67,7 +67,7 @@ export type UpdateEntryProps = { locale: string; gqlParams?: GraphQLParams; getStore: GetStore; - depth: number; + maxDepth: number; }; export type UpdateFieldProps = { @@ -77,7 +77,7 @@ export type UpdateFieldProps = { locale: string; gqlParams?: GraphQLParams; getStore: GetStore; - depth: number; + maxDepth: number; }; export type UpdateReferenceFieldProps = { @@ -86,7 +86,7 @@ export type UpdateReferenceFieldProps = { locale: string; gqlParams?: GraphQLParams; getStore: GetStore; - depth: number; + maxDepth: number; }; /**