@kontent-ai/smart-link
Version:
Kontent.ai Smart Link SDK allowing to automatically inject [smart links](https://docs.kontent.ai/tutorials/develop-apps/build-strong-foundation/set-up-editing-from-preview#a-using-smart-links) to Kontent.ai according to manually specified [HTML data attri
260 lines • 14.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyUpdateOnItemAndLoadLinkedItems = exports.applyUpdateOnItem = void 0;
const delivery_sdk_1 = require("@kontent-ai/delivery-sdk");
const optionallyAsync_1 = require("./liveReload/optionallyAsync");
/**
* Applies an update to a content item synchronously.
* This function takes a content item and an update message, and returns a new content item with the updates applied.
* The update is applied recursively to all linked items within the content item.
*/
const applyUpdateOnItem = (item, update) => (0, optionallyAsync_1.evaluateOptionallyAsync)(applyUpdateOnItemOptionallyAsync(item, update), null);
exports.applyUpdateOnItem = applyUpdateOnItem;
/**
* Applies an update to a content item asynchronously and loads newly added linked items.
* This function takes a content item, an update message, and a function to fetch linked items,
* and returns a promise that resolves to a new content item with the updates applied.
* The update is applied recursively to all linked items within the content item.
*/
const applyUpdateOnItemAndLoadLinkedItems = async (item, update, fetchItems) => Promise.resolve((0, optionallyAsync_1.evaluateOptionallyAsync)(applyUpdateOnItemOptionallyAsync(item, update), fetchItems));
exports.applyUpdateOnItemAndLoadLinkedItems = applyUpdateOnItemAndLoadLinkedItems;
const applyUpdateOnItemOptionallyAsync = (item, update, updatedItem = null, processedItemsPath = []) => {
const shouldApplyOnThisItem = item.system.codename === update.item.codename &&
item.system.language === update.variant.codename;
const newUpdatedItem = !updatedItem && shouldApplyOnThisItem ? { ...item } : updatedItem; // We will mutate its elements to new values before returning. This is necesary to preserve cyclic dependencies between items without infinite recursion.
const updatedElements = (0, optionallyAsync_1.mergeOptionalAsyncs)(Object.entries(item.elements).map(([elementCodename, element]) => {
const matchingUpdate = update.elements.find((u) => u.element.codename === elementCodename);
if (shouldApplyOnThisItem && matchingUpdate) {
return (0, optionallyAsync_1.applyOnOptionallyAsync)(applyUpdateOnElement(element, matchingUpdate), (newElement) => [elementCodename, newElement]);
}
if (element.type === delivery_sdk_1.ElementType.ModularContent || element.type === delivery_sdk_1.ElementType.RichText) {
const typedItemElement = element;
return (0, optionallyAsync_1.applyOnOptionallyAsync)((0, optionallyAsync_1.mergeOptionalAsyncs)(typedItemElement.linkedItems.map((i) => {
if (updatedItem?.system.codename === i.system.codename) {
// we closed the cycle and on the updated item and need to connect to the new item
return (0, optionallyAsync_1.createOptionallyAsync)(() => updatedItem);
}
return closesCycleWithoutUpdate(processedItemsPath, i.system.codename, updatedItem?.system.codename ?? null)
? (0, optionallyAsync_1.createOptionallyAsync)(() => i) // we found a cycle that doesn't need any update so we just ignore it
: applyUpdateOnItemOptionallyAsync(i, update, newUpdatedItem, [
...processedItemsPath,
i.system.codename,
]);
})), (linkedItems) => {
return linkedItems.some((newItem, index) => newItem !== typedItemElement.linkedItems[index])
? [elementCodename, { ...typedItemElement, linkedItems }]
: [elementCodename, typedItemElement];
});
}
return (0, optionallyAsync_1.createOptionallyAsync)(() => [elementCodename, element]);
}));
return (0, optionallyAsync_1.applyOnOptionallyAsync)(updatedElements, (newElements) => {
if (newUpdatedItem?.system.codename === item.system.codename) {
newUpdatedItem.elements = Object.fromEntries(newElements);
return newUpdatedItem;
}
return newElements.some(([codename, newEl]) => item.elements[codename] !== newEl)
? { ...item, elements: Object.fromEntries(newElements) }
: item;
});
};
const closesCycleWithoutUpdate = (path, nextItem, updatedItem) => {
const cycleStartIndex = path.indexOf(nextItem);
return cycleStartIndex !== -1 && (!updatedItem || cycleStartIndex > path.indexOf(updatedItem));
};
const applyUpdateOnElement = (element, update) => {
switch (update.type) {
case delivery_sdk_1.ElementType.Text:
case delivery_sdk_1.ElementType.Number:
case delivery_sdk_1.ElementType.UrlSlug:
return (0, optionallyAsync_1.createOptionallyAsync)(() => applySimpleElement(element, update));
case delivery_sdk_1.ElementType.ModularContent:
return applyLinkedItemsElement(element, update);
case delivery_sdk_1.ElementType.RichText:
return applyRichTextElement(element, update);
case delivery_sdk_1.ElementType.MultipleChoice:
return (0, optionallyAsync_1.createOptionallyAsync)(() => applyArrayElement(element, update, (o1, o2) => o1?.codename === o2?.codename));
case delivery_sdk_1.ElementType.DateTime:
return (0, optionallyAsync_1.createOptionallyAsync)(() => applyDateTimeElement(element, update));
case delivery_sdk_1.ElementType.Asset:
return (0, optionallyAsync_1.createOptionallyAsync)(() => applyArrayElement(element, update, (a1, a2) => a1?.url === a2?.url));
case delivery_sdk_1.ElementType.Taxonomy:
return (0, optionallyAsync_1.createOptionallyAsync)(() => applyArrayElement(element, update, (t1, t2) => t1?.codename === t2?.codename));
case delivery_sdk_1.ElementType.Custom:
return (0, optionallyAsync_1.createOptionallyAsync)(() => applyCustomElement(element, update));
default:
return (0, optionallyAsync_1.createOptionallyAsync)(() => element);
}
};
const applyCustomElement = (element, update) => typeof element.value === "string" && element.value !== update.data.value
? { ...element, value: update.data.value }
: element;
const applyDateTimeElement = (element, update) => element.value === update.data.value && element.displayTimeZone === update.data.displayTimeZone
? element
: { ...element, value: update.data.value, displayTimeZone: update.data.displayTimeZone };
const applySimpleElement = (element, update) => element.value === update.data.value ? element : { ...element, value: update.data.value };
const applyArrayElement = (element, update, areSame) => element.value.length === update.data.value.length &&
element.value.every((el, i) => areSame(el, update.data.value[i]))
? element
: { ...element, value: update.data.value };
const applyLinkedItemsElement = (element, update) => {
if (areLinkedItemsSame(element.value, update.data.value)) {
return (0, optionallyAsync_1.createOptionallyAsync)(() => element);
}
return (0, optionallyAsync_1.applyOnOptionallyAsync)(updateLinkedItems(update.data.value, element.linkedItems), (linkedItems) => ({
...element,
value: update.data.value,
linkedItems,
}));
};
const applyRichTextElement = (element, update) => {
if (areRichTextElementsSame(element, update.data)) {
return (0, optionallyAsync_1.createOptionallyAsync)(() => element);
}
const withItems = (0, optionallyAsync_1.applyOnOptionallyAsync)(updateLinkedItems(update.data.linkedItemCodenames, update.data.linkedItems
.filter((i) => !element.linkedItems.find((u) => u.system.codename === i.system.codename))
.concat(element.linkedItems)), (linkedItems) => ({
...element,
value: update.data.value,
linkedItemCodenames: update.data.linkedItemCodenames,
links: update.data.links,
images: update.data.images,
linkedItems,
}));
return (0, optionallyAsync_1.chainOptionallyAsync)(withItems, (el) => (0, optionallyAsync_1.applyOnOptionallyAsync)(updateComponents(update.data.linkedItems, el.linkedItems), (linkedItems) => ({
...el,
linkedItems,
})));
};
const areItemsSame = (item1, item2) => item1.system.codename === item2.system.codename &&
item1.system.language === item2.system.language &&
Object.entries(item1.elements).every(([codename, el1]) => areElementsSame(el1, item2.elements[codename]));
const areElementsSame = (el1, el2) => {
switch (el1.type) {
case delivery_sdk_1.ElementType.Text:
case delivery_sdk_1.ElementType.Number:
case delivery_sdk_1.ElementType.UrlSlug:
return el1.value === el2.value;
case delivery_sdk_1.ElementType.MultipleChoice: {
const typedElement1 = el1;
const typedElement2 = el2;
return (typedElement1.value.length === typedElement2.value.length &&
typedElement1.value.every((option, i) => option.codename === typedElement2.value[i]?.codename));
}
case delivery_sdk_1.ElementType.DateTime: {
const typedElement1 = el1;
const typedElement2 = el2;
return (typedElement1.value === typedElement2.value &&
typedElement1.displayTimeZone === typedElement2.displayTimeZone);
}
case delivery_sdk_1.ElementType.RichText: {
const typedElement1 = el1;
const typedElement2 = el2;
return areRichTextElementsSame(typedElement1, typedElement2);
}
case delivery_sdk_1.ElementType.Taxonomy: {
const typedElement1 = el1;
const typedElement2 = el2;
return (typedElement1.value.length === typedElement2.value.length &&
typedElement1.value.every((term, i) => term.codename === typedElement2.value[i].codename));
}
case delivery_sdk_1.ElementType.Asset: {
const typedElement1 = el1;
const typedElement2 = el2;
return (typedElement1.value.length === typedElement2.value.length &&
typedElement1.value.every((asset, i) => asset.url === typedElement2.value[i].url));
}
case delivery_sdk_1.ElementType.ModularContent: {
const typedElement1 = el1;
const typedElement2 = el2;
return (typedElement1.value.length === typedElement2.value.length &&
typedElement1.value.every((item, i) => item === typedElement2.value[i]));
}
case delivery_sdk_1.ElementType.Custom:
return el1.value === el2.value;
case delivery_sdk_1.ElementType.Unknown:
throw new Error();
}
};
const areRichTextElementsSame = (el1, el2) => el1.value === el2.value &&
el1.links.length === el2.links.length &&
el1.links.every((link, i) => link.codename === el2.links[i].codename) &&
el1.images.length === el2.images.length &&
el1.images.every((image, i) => image.url === el2.images[i].url) &&
el1.linkedItemCodenames.length === el2.linkedItemCodenames.length &&
el1.linkedItemCodenames.every((codename, i) => codename === el2.linkedItemCodenames[i]) &&
el1.linkedItems.length === el2.linkedItems.length &&
el1.linkedItems.every((item, i) => areItemsSame(item, el2.linkedItems[i]));
const updateComponents = (newItems, oldItems) => (0, optionallyAsync_1.mergeOptionalAsyncs)(oldItems.map((item) => {
const newItem = newItems.find((i) => i.system.codename === item.system.codename);
return newItem
? applyUpdateOnItemOptionallyAsync(item, convertItemToUpdate(newItem))
: (0, optionallyAsync_1.createOptionallyAsync)(() => item);
}));
const updateLinkedItems = (newValue, loadedItems) => {
const itemsByCodename = new Map(loadedItems.map((i) => [i.system.codename, i]));
const newLinkedItems = newValue.map((codename) => itemsByCodename.get(codename) ?? codename);
const itemsToFetch = newLinkedItems.filter(isString);
return (0, optionallyAsync_1.applyOnOptionallyAsync)((0, optionallyAsync_1.createOptionallyAsync)(async (fetchItems) => fetchItems && itemsToFetch.length ? await fetchItems(itemsToFetch) : []), (fetchedItemsArray) => {
const fetchedItems = new Map(fetchedItemsArray.map((i) => [i.system.codename, i]));
return newLinkedItems
.map((codename) => (isString(codename) ? (fetchedItems.get(codename) ?? null) : codename))
.filter(notNull);
});
};
const areLinkedItemsSame = (items1, items2) => items1.length === items2.length && items1.every((codename, index) => codename === items2[index]);
const notNull = (value) => value !== null;
const isString = (value) => typeof value === "string";
const convertItemToUpdate = (item) => ({
variant: { codename: item.system.language },
item: { codename: item.system.codename },
elements: Object.entries(item.elements).map(([elCodename, el]) => {
switch (el.type) {
case delivery_sdk_1.ElementType.Number:
case delivery_sdk_1.ElementType.UrlSlug:
case delivery_sdk_1.ElementType.MultipleChoice:
case delivery_sdk_1.ElementType.Custom:
case delivery_sdk_1.ElementType.Asset:
case delivery_sdk_1.ElementType.Text: {
return {
element: { codename: elCodename },
type: el.type,
data: el,
};
}
case delivery_sdk_1.ElementType.DateTime: {
return {
element: { codename: elCodename },
type: el.type,
data: el,
};
}
case delivery_sdk_1.ElementType.RichText: {
return {
element: { codename: elCodename },
type: el.type,
data: el,
};
}
case delivery_sdk_1.ElementType.Taxonomy: {
return {
element: { codename: elCodename },
type: el.type,
data: el,
};
}
case delivery_sdk_1.ElementType.ModularContent: {
return {
element: { codename: elCodename },
type: el.type,
data: el,
};
}
case delivery_sdk_1.ElementType.Unknown:
throw new Error(`Cannot update element of type ${el.type}.`);
default:
throw new Error(`Unknown element type`);
}
}),
});
//# sourceMappingURL=liveReload.js.map