Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions apps/rich-text-versioning/src/components/HtmlDiffViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,24 @@ interface HtmlDiffViewerProps {
currentField: Document;
publishedField: Document;
onChangeCount: (count: number) => void;
entries: EntryProps[];
entryContentTypes: Record<string, ContentTypeProps>;
entriesFromPublished: EntryProps[];
entriesFromCurrent: EntryProps[];
entryContentTypes: ContentTypeProps[];
locale: string;
assets: AssetProps[];
assetsFromPublished: AssetProps[];
assetsFromCurrent: AssetProps[];
}

const HtmlDiffViewer = ({
currentField,
publishedField,
onChangeCount,
entries,
entriesFromPublished,
entriesFromCurrent,
entryContentTypes,
locale,
assets,
assetsFromPublished,
assetsFromCurrent,
}: HtmlDiffViewerProps) => {
const [diffHtml, setDiffHtml] = useState<string>('');

Expand All @@ -35,11 +39,11 @@ const HtmlDiffViewer = ({
// Convert current field to React components with embedded entry renderers
const currentComponents = documentToReactComponents(
currentField,
createOptions(entries, entryContentTypes, assets, locale)
createOptions(entriesFromCurrent, entryContentTypes, assetsFromCurrent, locale)
);
const publishedComponents = documentToReactComponents(
publishedField,
createOptions(entries, entryContentTypes, assets, locale)
createOptions(entriesFromPublished, entryContentTypes, assetsFromPublished, locale)
);

// Convert React components to HTML strings
Expand All @@ -66,7 +70,17 @@ const HtmlDiffViewer = ({
};

processDiff();
}, [currentField, publishedField, onChangeCount, entries, entryContentTypes, locale, assets]);
}, [
currentField,
publishedField,
onChangeCount,
entriesFromCurrent,
entriesFromPublished,
entryContentTypes,
locale,
assetsFromCurrent,
assetsFromPublished,
]);

if (!diffHtml) {
return (
Expand Down
10 changes: 7 additions & 3 deletions apps/rich-text-versioning/src/components/createOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const ASSET_NOT_FOUND = 'Asset missing or inaccessible';

export const createOptions = (
entries: EntryProps[],
entryContentTypes: Record<string, ContentTypeProps>,
entryContentTypes: ContentTypeProps[],
assets: AssetProps[],
locale: string
): Options => ({
Expand All @@ -24,7 +24,9 @@ export const createOptions = (
</Box>
);
}
const contentType = entryContentTypes[entry.sys.id];
const contentType = entryContentTypes.find(
(ct) => ct.sys.id === entry.sys.contentType.sys.id
);
const contentTypeName = contentType?.name || UNKNOWN;
const title = getEntryTitle(entry, contentType, locale);

Expand All @@ -41,7 +43,9 @@ export const createOptions = (
return <InlineEntryCard contentType={UNKNOWN}>{ENTRY_NOT_FOUND}</InlineEntryCard>;
}

const contentType = entryContentTypes[entry.sys.id];
const contentType = entryContentTypes.find(
(ct) => ct.sys.id === entry.sys.contentType.sys.id
);
const contentTypeName = contentType?.name || UNKNOWN;
const title = getEntryTitle(entry, contentType, locale);

Expand Down
112 changes: 63 additions & 49 deletions apps/rich-text-versioning/src/locations/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ const Dialog = () => {
const sdk = useSDK<DialogAppSDK>();
const invocationParams = sdk.parameters.invocation as unknown as InvocationParameters;
const [changeCount, setChangeCount] = useState(0);
const [entries, setEntries] = useState<EntryProps[]>([]);
const [entryContentTypes, setEntryContentTypes] = useState<Record<string, ContentTypeProps>>({});
const [assets, setAssets] = useState<AssetProps[]>([]);
const [entriesFromPublished, setEntriesFromPublished] = useState<EntryProps[]>([]);
const [entriesFromCurrent, setEntriesFromCurrent] = useState<EntryProps[]>([]);
const [entryContentTypes, setEntryContentTypes] = useState<ContentTypeProps[]>([]);
const [assetsFromPublished, setAssetsFromPublished] = useState<AssetProps[]>([]);
const [assetsFromCurrent, setAssetsFromCurrent] = useState<AssetProps[]>([]);
const [loading, setLoading] = useState(true);

useAutoResizer();
Expand All @@ -43,11 +45,11 @@ const Dialog = () => {
const publishedField = invocationParams?.publishedField || { content: [] };
const locale = invocationParams?.locale;

const getReferenceIdsFromDocument = (doc: Document, types: string[]): string[] => {
const ids: string[] = [];
const getReferenceIdsFromDocument = (doc: Document, types: string[]): Set<string> => {
const ids: Set<string> = new Set();
const getEntryIdsFromNode = (node: any) => {
if (types.includes(node.nodeType)) {
ids.push(node.data.target.sys.id);
ids.add(node.data.target.sys.id);
}
if ('content' in node) {
node.content.forEach(getEntryIdsFromNode);
Expand All @@ -57,10 +59,42 @@ const Dialog = () => {
return ids;
};

const getAssets = async (assetIds: string[]): Promise<AssetProps[]> => {
if (assetIds.length === 0) return [];

try {
const fetchedAssets = await sdk.cma.asset.getMany({
query: {
select: 'sys.id,fields.title',
'sys.id[in]': assetIds.join(','),
},
});
return fetchedAssets.items;
} catch (error) {
console.error('Error fetching assets:', error);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 should we notify users? So they know that something went wrong. Also in the getEntries and the content type fetch?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're already doing that 😎 In the createOptions function if we can't identify the entry, asset or content type we just display it as unknown. This just logs the specific error in the console to be able to debug

return [];
}
};

const getEntries = async (entryIds: string[]): Promise<EntryProps[]> => {
if (entryIds.length === 0) return [];

try {
const fetchedEntries = await sdk.cma.entry.getMany({
query: {
'sys.id[in]': entryIds.join(','),
},
});
return fetchedEntries.items;
} catch (error) {
console.error('Error fetching entries:', error);
return [];
}
};

useEffect(() => {
const fetchReferences = async () => {
setLoading(true);
const contentTypes: Record<string, ContentTypeProps> = {};

const currentEntryIds = getReferenceIdsFromDocument(currentField, [
BLOCKS.EMBEDDED_ENTRY,
Expand All @@ -74,53 +108,31 @@ const Dialog = () => {
const publishedAssetIds = getReferenceIdsFromDocument(publishedField, [
BLOCKS.EMBEDDED_ASSET,
]);
const allEntryIds = [...new Set([...currentEntryIds, ...publishedEntryIds])];
const allAssetIds = [...new Set([...currentAssetIds, ...publishedAssetIds])];

if (allEntryIds.length > 0) {
let entries: EntryProps[] = [];
if (currentEntryIds.size > 0 || publishedEntryIds.size > 0) {
const fetchedEntriesFromCurrent = await getEntries(Array.from(currentEntryIds));
const fetchedEntriesFromPublished = await getEntries(Array.from(publishedEntryIds));
setEntriesFromCurrent(fetchedEntriesFromCurrent);
setEntriesFromPublished(fetchedEntriesFromPublished);

try {
const fetchedEntries = await sdk.cma.entry.getMany({
const allEntries = [...entriesFromCurrent, ...entriesFromPublished];
const fetchedContentTypes = await sdk.cma.contentType.getMany({
query: {
'sys.id[in]': allEntryIds.join(','),
'sys.id[in]': allEntries.map((entry) => entry.sys.contentType.sys.id),
},
});
entries = fetchedEntries.items;
setEntries(entries);
setEntryContentTypes(fetchedContentTypes.items);
} catch (error) {
entries = [];
console.error('Error fetching entries:', error);
console.error('Error fetching content types:', error);
}
if (entries.length > 0) {
await Promise.all(
entries.map(async (entry) => {
const entryId = entry.sys.id;
try {
const contentType = await sdk.cma.contentType.get({
contentTypeId: entry.sys.contentType.sys.id,
});

contentTypes[entryId] = contentType;
} catch (error) {
console.error(`Error fetching content type for entry ${entryId}:`, error);
contentTypes[entryId] = { name: 'Reference is missing' } as ContentTypeProps;
}
})
);
}
setEntryContentTypes(contentTypes);
}
if (allAssetIds.length > 0) {
try {
const fetchedAssets = await sdk.cma.asset.getMany({
query: {
'sys.id[in]': allAssetIds.join(','),
},
});
setAssets(fetchedAssets.items);
} catch (error) {
console.error('Error fetching assets:', error);
}
if (publishedAssetIds.size > 0 || currentAssetIds.size > 0) {
const fetchedAssetsFromPublished = await getAssets(Array.from(publishedAssetIds));
const fetchedAssetsFromCurrent = await getAssets(Array.from(currentAssetIds));

setAssetsFromPublished(fetchedAssetsFromPublished);
setAssetsFromCurrent(fetchedAssetsFromCurrent);
}
setLoading(false);
};
Expand Down Expand Up @@ -188,10 +200,12 @@ const Dialog = () => {
currentField={currentField}
publishedField={publishedField}
onChangeCount={setChangeCount}
entries={entries}
entriesFromPublished={entriesFromPublished}
entriesFromCurrent={entriesFromCurrent}
entryContentTypes={entryContentTypes}
locale={sdk.locales.default}
assets={assets}
assetsFromPublished={assetsFromPublished}
assetsFromCurrent={assetsFromCurrent}
/>
)}
</GridItem>
Expand All @@ -200,7 +214,7 @@ const Dialog = () => {
<Box className={styles.diff}>
{documentToReactComponents(
publishedField,
createOptions(entries, entryContentTypes, assets, locale)
createOptions(entriesFromPublished, entryContentTypes, assetsFromPublished, locale)
)}
</Box>
</GridItem>
Expand Down
15 changes: 7 additions & 8 deletions apps/rich-text-versioning/src/locations/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,16 @@ const Field = () => {

try {
const publishedEntries = await sdk.cma.entry.getPublished({
query: { 'sys.id': sdk.ids.entry },
query: {
'sys.id': sdk.ids.entry,
include: 0,
},
});
const publishedEntry = publishedEntries.items[0];

if (publishedEntry?.fields?.[sdk.field.id]) {
const fieldData = publishedEntry.fields[sdk.field.id];
publishedField = fieldData as Document;
}
publishedField = publishedEntry?.fields?.[sdk.field.id]?.[sdk.field.locale];
} catch (error) {
console.error('Error loading content:', error);
currentErrorInfo = {
hasError: true,
errorCode: '500',
Expand All @@ -81,9 +82,7 @@ const Field = () => {
shouldCloseOnEscapePress: true,
parameters: {
currentField: convertToSerializableJson(value),
publishedField: publishedField
? convertToSerializableJson(publishedField)[sdk.field.locale]
: undefined,
publishedField: publishedField ? convertToSerializableJson(publishedField) : undefined,
errorInfo: convertToSerializableJson(currentErrorInfo),
locale: sdk.field.locale,
},
Expand Down
7 changes: 3 additions & 4 deletions apps/rich-text-versioning/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { AppState, ContentTypeField } from '@contentful/app-sdk';
import { EntityStatus } from '@contentful/f36-components';
import { ContentTypeProps, Entry, EntryProps } from 'contentful-management';
import { ContentTypeProps, EntryProps } from 'contentful-management';

import { Document } from '@contentful/rich-text-types';

Expand Down Expand Up @@ -60,10 +59,10 @@ export const restoreSelectedFields = (

export const getEntryTitle = (
entry: EntryProps,
contentType: ContentTypeProps,
contentType: ContentTypeProps | undefined,
locale: string
): string => {
let displayFieldId = contentType.displayField;
const displayFieldId = contentType?.displayField;
if (!displayFieldId) return 'Untitled';

const value = entry.fields[displayFieldId]?.[locale];
Expand Down
Loading