UNPKG

@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
"use strict"; 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