summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/today/today-parse-util.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/today/today-parse-util.js')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/today/today-parse-util.js1047
1 files changed, 1047 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/today/today-parse-util.js b/node_modules/@jet-app/app-store/tmp/src/common/today/today-parse-util.js
new file mode 100644
index 0000000..a1e27b0
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/today/today-parse-util.js
@@ -0,0 +1,1047 @@
+import { isNothing, isSome } from "@jet/environment";
+import * as validation from "@jet/environment/json/validation";
+import * as models from "../../api/models";
+import { asArrayOrEmpty, asBoolean, asDictionary, asInterface, asString, isDefinedNonNull, isDefinedNonNullNonEmpty, isNull, isNullOrEmpty, } from "../../foundation/json-parsing/server-data";
+import { editorialCardFromData } from "../../foundation/media/associations";
+import * as mediaAttributes from "../../foundation/media/attributes";
+import { attributeAsString, hasAttributes } from "../../foundation/media/attributes";
+import * as mediaDataStructure from "../../foundation/media/data-structure";
+import { relationshipData } from "../../foundation/media/relationships";
+import { Parameters, Path, Protocol } from "../../foundation/network/url-constants";
+import { URL } from "../../foundation/network/urls";
+import { isAdPlacementEnabled } from "../ads/ad-common";
+import { categoryArtworkData } from "../categories";
+import { artworkFromApiArtwork, iconFromData } from "../content/content";
+import { extractEditorialClientParams } from "../editorial-pages/editorial-data-util";
+import { addImpressionFields } from "../metrics/helpers/impressions";
+import { nextPosition, popLocation, pushContentLocation } from "../metrics/helpers/location";
+import { combinedRecoMetricsDataFromMetricsData } from "../metrics/helpers/util";
+import * as onDevicePersonalization from "../personalization/on-device-personalization";
+import * as color from "./../../foundation/util/color-util";
+import { asNumber } from "@apple-media-services/media-api";
+import { createTodayAdCard } from "./cards/today-ad-card-builder";
+import { defaultTodayCardConfiguration, todayCardFromData } from "./today-card-util";
+import * as impressionDemotion from "../../common/personalization/on-device-impression-demotion";
+import { EditorialBackgroundType, TodayCardMetricsDisplayStyle, TodayHeaderArtworkBehavior, TodaySectionHeaderArtworkPlacement, } from "./today-types";
+/**
+ * We try to flatten the today response so we can treat it as a single list
+ * of items. These are the different item types in the flattened list.
+ */
+export var FlattenedTodayItemType;
+(function (FlattenedTodayItemType) {
+ FlattenedTodayItemType["EditorialItem"] = "editorialItem";
+ FlattenedTodayItemType["EditorialItemGroup"] = "editorialItemGroup";
+})(FlattenedTodayItemType || (FlattenedTodayItemType = {}));
+/**
+ * Flatten the entire modules array from the TodayPage response into a single list of items.
+ * @param objectGraph The dependency graph for the App Store
+ * @param todayModules The today modules to flatten from the page response
+ * @param onboardingCardsData The media api data for the onboarding cards, inserted into the flattened list
+ * @param todayRecommendations The today recommendations result, inserted into the flattened list
+ * @param adsResponse The response from ad platforms
+ * @returns The flattened list of today items
+ */
+export function flattenTodayModules(objectGraph, todayModules, todayRecommendations, onboardingCardsData, adsResponse) {
+ const flattenedItems = [];
+ let insertedOnboardingCards = isNullOrEmpty(onboardingCardsData);
+ // Retrieve our personalization data
+ const personalizationDataContainer = personalizationDataContainerForTodayModules(objectGraph, todayModules);
+ let absoluteFeedIndex = 0;
+ for (const todayModule of todayModules) {
+ if (isNullOrEmpty(todayModule.contents)) {
+ continue;
+ }
+ const personalizedModuleDataResult = onDevicePersonalization.personalizeDataItems(objectGraph, "today", todayModule.contents, true, personalizationDataContainer);
+ todayModule.contents = personalizedModuleDataResult.personalizedData;
+ todayModule.onDevicePersonalizationProcessingType = personalizedModuleDataResult.processingType;
+ let isFirstItemInModule = true;
+ const moduleMetadata = {
+ label: todayModule.label,
+ title: todayModule.title,
+ meta: todayModule.meta,
+ date: todayModule.date,
+ onDevicePersonalizationProcessingType: todayModule.onDevicePersonalizationProcessingType,
+ isTodayForAppsModule: mediaDataStructure.isModuleTodayForApps(todayModule),
+ };
+ if (!insertedOnboardingCards) {
+ for (const onboardingCardData of onboardingCardsData) {
+ flattenedItems.push({
+ type: FlattenedTodayItemType.EditorialItem,
+ data: onboardingCardData,
+ isDataHydrated: hasAttributes(onboardingCardData),
+ isFirstItemInModule,
+ moduleMetadata: { ...moduleMetadata },
+ containedAdSlots: [absoluteFeedIndex],
+ });
+ }
+ isFirstItemInModule = false;
+ insertedOnboardingCards = true;
+ absoluteFeedIndex += 1;
+ }
+ for (let moduleItem of todayModule.contents) {
+ const onDeviceUseCase = asString(moduleItem, "meta.personalizationData.onDeviceUseCase");
+ switch (moduleItem.type) {
+ case "editorial-items":
+ if (isSome(onDeviceUseCase)) {
+ // ODP personalization for Today Arcade stories.
+ const storyData = todayRecommendations === null || todayRecommendations === void 0 ? void 0 : todayRecommendations.storyData(onDeviceUseCase);
+ if (isSome(storyData)) {
+ moduleItem = storyData;
+ }
+ }
+ flattenedItems.push({
+ type: FlattenedTodayItemType.EditorialItem,
+ data: moduleItem,
+ isDataHydrated: hasAttributes(moduleItem),
+ isFirstItemInModule,
+ moduleMetadata: { ...moduleMetadata },
+ containedAdSlots: [absoluteFeedIndex],
+ });
+ isFirstItemInModule = false;
+ absoluteFeedIndex += 1;
+ break;
+ case "editorial-item-groups":
+ const groupContents = asArrayOrEmpty(moduleItem, "meta.associations.recommendations.data");
+ if (isNullOrEmpty(groupContents)) {
+ continue;
+ }
+ let storyGroupData;
+ if (isSome(onDeviceUseCase)) {
+ // ODP personalization for Today Arcade story groups.
+ storyGroupData = todayRecommendations === null || todayRecommendations === void 0 ? void 0 : todayRecommendations.storyGroupData(onDeviceUseCase);
+ }
+ if (isSome(storyGroupData)) {
+ moduleItem = storyGroupData;
+ }
+ else {
+ // ODP personalization for in-app event story groups.
+ const personalizedStoryGroupDataResult = onDevicePersonalization.personalizeDataItems(objectGraph, "today", groupContents, true, personalizationDataContainer);
+ moduleItem["meta"]["associations"]["recommendations"]["data"] =
+ personalizedStoryGroupDataResult.personalizedData;
+ todayModule.onDevicePersonalizationProcessingType =
+ personalizedStoryGroupDataResult.processingType;
+ }
+ flattenedItems.push({
+ type: FlattenedTodayItemType.EditorialItemGroup,
+ data: moduleItem,
+ isDataHydrated: hasAttributes(moduleItem),
+ isFirstItemInModule,
+ moduleMetadata: { ...moduleMetadata },
+ containedAdSlots: Array.from({ length: groupContents.length }, (key, value) => value + absoluteFeedIndex),
+ });
+ isFirstItemInModule = false;
+ absoluteFeedIndex += groupContents.length;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return flattenedItems;
+}
+/**
+ * Whether the next content item in the flattened list is hydrated.
+ * @param flattenedItems The flattened list of today items
+ * @returns True if the next content item is hydrated, false otherwise
+ */
+export function nextFlattenedItemIsHydrated(flattenedItems) {
+ for (const flattenedItem of flattenedItems) {
+ switch (flattenedItem.type) {
+ case FlattenedTodayItemType.EditorialItem:
+ case FlattenedTodayItemType.EditorialItemGroup:
+ return hasAttributes(flattenedItem.data);
+ default:
+ break;
+ }
+ }
+ return false;
+}
+/**
+ * Iterates through all the today modules, and creates a set of personalization
+ * data that is targetted only to the contents of these modules.
+ *
+ * @param dataArray The input array of today modules.
+ * @returns Any relevant OnDevicePersonalizaionData
+ */
+export function personalizationDataContainerForTodayModules(objectGraph, todayModules) {
+ if (!onDevicePersonalization.isPersonalizationAvailable(objectGraph)) {
+ return null;
+ }
+ // Here we iterate through all the modules, and look inside each content items meta for personalizationData.
+ const appIds = new Set();
+ for (const todayModule of todayModules) {
+ if (isNull(todayModule.contents)) {
+ continue;
+ }
+ const appIdFromContent = (contentData) => {
+ return asString(contentData, "meta.personalizationData.appId");
+ };
+ for (const contentData of todayModule.contents) {
+ switch (contentData.type) {
+ case "editorial-item-groups":
+ const groupItems = asArrayOrEmpty(contentData.meta, "associations.recommendations.data");
+ for (const groupItem of groupItems) {
+ const appId = appIdFromContent(groupItem);
+ if (isDefinedNonNullNonEmpty(appId)) {
+ appIds.add(appId.toString());
+ }
+ }
+ break;
+ default:
+ const appId = appIdFromContent(contentData);
+ if (isDefinedNonNullNonEmpty(appId)) {
+ appIds.add(appId.toString());
+ }
+ break;
+ }
+ }
+ }
+ return onDevicePersonalization.personalizationDataContainerForAppIds(objectGraph, appIds);
+}
+// MARK: - Shelf Creation
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param todayItem The today item that this card is contained in, this could be a single item or a story group item
+ * @param todayCardData The MAPI data that will be used to create this today card
+ * @param isAdEligible Whether the current card is being placed in a slot that was eligible for an ad
+ * @param currentRowIndex The current row index this card is going to be in
+ * @param metricsDisplayStyle: The display style for impressions and location
+ * @param isHeroCard: Whether this is the first card in a hero story group
+ * @returns The configuration to use when parsing a today card
+ */
+function createTodayCardConfiguration(objectGraph, todayCardData, isAdEligible, currentRowIndex, metricsDisplayStyle, isHeroCard) {
+ var _a;
+ const cardConfig = defaultTodayCardConfiguration(objectGraph);
+ cardConfig.useOTDTextStyle = (_a = asBoolean(todayCardData, "meta.personalizationData.isOfTheDay")) !== null && _a !== void 0 ? _a : false;
+ cardConfig.replaceIfAdPresent = asBoolean(todayCardData, "meta.personalizationData.replaceIfAdPresent");
+ cardConfig.isAdEligible = isAdEligible;
+ cardConfig.currentRowIndex = currentRowIndex;
+ cardConfig.metricsDisplayStyle = metricsDisplayStyle;
+ cardConfig.isHeroCard = isHeroCard;
+ if (objectGraph.client.isWeb) {
+ cardConfig.prevailingCropCodes = {
+ "defaultCrop": "sr",
+ "editorialArtwork.dayCard": "grav.west",
+ };
+ }
+ return cardConfig;
+}
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param pageContext The page context for the today page
+ * @returns The currentRowIndex from the pageContext, this is only used on iPhone, otherwise return
+ * null since we cant reliably know the row index
+ */
+function rowCountFromContext(objectGraph, pageContext) {
+ if (!objectGraph.client.isPhone) {
+ return undefined;
+ }
+ return pageContext.currentRowIndex;
+}
+/**
+ * Update the page context with the current row count, this uses the groupDisplayStyle to determine
+ * the display style for the cards in the current row
+ * @param pageContext The page context for the today page we're parsing
+ * @param currentGroupDisplayStyle The current groupDisplayItem from the TodayItem we're parsing
+ * @param currentItemIndex The index of the current item in the shelf, that we just created a card
+ * for, this method is called after creating a card
+ */
+function incrementRowIndexIfNecessary(pageContext, currentGroupDisplayStyle, currentItemIndex) {
+ switch (currentGroupDisplayStyle) {
+ case models.GroupDisplayStyle.Grid:
+ if (currentItemIndex % 2 === 1) {
+ pageContext.currentRowIndex++;
+ }
+ break;
+ case models.GroupDisplayStyle.Hero:
+ if (currentItemIndex === 0 || (currentItemIndex - 1) % 2 === 1) {
+ pageContext.currentRowIndex++;
+ }
+ break;
+ case models.GroupDisplayStyle.Standard:
+ pageContext.currentRowIndex++;
+ break;
+ default:
+ break;
+ }
+}
+/**
+ * Update the page context with the current metrics display style, this is based off the groupDisplayStyle
+ * @param objectGraph The dependency graph for the App Store
+ * @param pageContext The page context for the today page we're parsing
+ * @param currentGroupDisplayStyle The current groupDisplayItem from the TodayItem we're parsing
+ * @param currentItemIndex The index of the current item in the shelf, that we just created a card
+ * for, this method is called after creating a card
+ */
+function updateCurrentRowMetricsDisplayStyle(objectGraph, pageContext, currentGroupDisplayStyle, currentItemIndex) {
+ if (objectGraph.client.isPad) {
+ pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.MediumCard;
+ return;
+ }
+ switch (currentGroupDisplayStyle) {
+ case models.GroupDisplayStyle.Grid:
+ pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.SmallCard;
+ break;
+ case models.GroupDisplayStyle.Hero:
+ if (currentItemIndex === 0) {
+ pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.MediumCard;
+ }
+ else {
+ pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.SmallCard;
+ }
+ break;
+ case models.GroupDisplayStyle.Standard:
+ pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.MediumCard;
+ break;
+ default:
+ break;
+ }
+}
+/**
+ * @param objectGraph The app store object graph
+ * @param data The data from MAPI for a single item in the today feed, group or single EI
+ * @returns The group display style for the given data
+ */
+function groupDisplayStyleFromData(objectGraph, data) {
+ var _a;
+ if (data.type === "editorial-items") {
+ return models.GroupDisplayStyle.Standard;
+ }
+ let groupDisplayStyle;
+ const editorialCard = editorialCardFromData(data);
+ if (hasAttributes(editorialCard)) {
+ groupDisplayStyle = mediaAttributes.attributeAsString(editorialCard, "editorialItemGroupDisplayStyle");
+ }
+ if (isNothing(groupDisplayStyle)) {
+ groupDisplayStyle =
+ (_a = mediaAttributes.attributeAsString(data, "displayStyle")) !== null && _a !== void 0 ? _a : models.GroupDisplayStyle.Standard;
+ }
+ return isGroupDisplayStyleSupported(objectGraph, groupDisplayStyle)
+ ? groupDisplayStyle
+ : models.GroupDisplayStyle.Standard;
+}
+/**
+ * @param objectGraph The app dependency graph
+ * @param displayStyle The display style to check support for
+ * @returns Whether group display style is supported for this platform
+ */
+function isGroupDisplayStyleSupported(objectGraph, displayStyle) {
+ if (isNothing(displayStyle)) {
+ return false;
+ }
+ switch (displayStyle) {
+ case models.GroupDisplayStyle.Grid:
+ return objectGraph.client.isPhone;
+ default:
+ return true;
+ }
+}
+/**
+ * Determine if the current card is eligible for an ad, based on the parsed card count, and the
+ * bag defined values for slots that can have ads
+ * @param pageContext The page context for the today page
+ * @returns Whether the current card is eligible for an ad
+ */
+function isTodayCardConfigurationAdEligible(pageContext) {
+ if (pageContext.adLocation === pageContext.parsedCardCount) {
+ return true;
+ }
+ if (isNothing(pageContext.eligibleAdLocations)) {
+ return false;
+ }
+ return pageContext.eligibleAdLocations.includes(pageContext.parsedCardCount);
+}
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param todayItem The flattened today item to create a shelf for
+ * @param pageContext The page context for the today page
+ * @returns The shelf for the given today item, or null if we cant create one
+ */
+export function todayShelfForEditorialItem(objectGraph, todayItem, pageContext) {
+ var _a;
+ const shelf = createTodayShelfWithItems(objectGraph, todayItem, pageContext, () => {
+ var _a, _b, _c;
+ const shelfItems = [];
+ (_a = pageContext.pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.updateContainerId((_b = pageContext.pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.containerIdForSlotIndex((_c = pageContext.parsedCardCount) !== null && _c !== void 0 ? _c : 0));
+ const groupDisplayStyle = groupDisplayStyleFromData(objectGraph, todayItem.data);
+ updateCurrentRowMetricsDisplayStyle(objectGraph, pageContext, groupDisplayStyle, 0);
+ const cardConfig = createTodayCardConfiguration(objectGraph, todayItem.data, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), pageContext.currentRowMetricsDisplayStyle, false);
+ cardConfig.baseMetricsOptions = {
+ recoMetricsData: recoMetricsFromTodayItem(todayItem),
+ };
+ const editorialItemTodayCard = createTodayCardForPageContext(objectGraph, pageContext, cardConfig, todayItem.data);
+ if (isNothing(editorialItemTodayCard)) {
+ return shelfItems;
+ }
+ shelfItems.push(editorialItemTodayCard);
+ nextPosition(pageContext.locationTracker);
+ pageContext.parsedCardCount++;
+ incrementRowIndexIfNecessary(pageContext, groupDisplayStyle, 0);
+ return shelfItems;
+ });
+ shelf.contentsMetadata = {
+ type: "todaySection",
+ debugSectionTypeIndicatorColor: todayItem.type === FlattenedTodayItemType.EditorialItemGroup
+ ? color.named("systemGreen")
+ : color.named("systemBlue"),
+ groupDisplayStyle: models.GroupDisplayStyle.Standard,
+ };
+ // If this is not the first item in the module, attempt to set the background
+ if (!todayItem.isFirstItemInModule) {
+ const editorialShelfBackgroundInfo = todaySectionEditorialBackground(objectGraph, todayItem);
+ if (isSome(editorialShelfBackgroundInfo)) {
+ shelf.background = editorialShelfBackgroundInfo.shelfBackground;
+ if (isSome((_a = shelf.header) === null || _a === void 0 ? void 0 : _a.configuration)) {
+ shelf.header.configuration.eyebrowColor = editorialShelfBackgroundInfo.eyebrowColor;
+ shelf.header.configuration.titleColor = editorialShelfBackgroundInfo.titleColor;
+ shelf.header.configuration.subtitleColor = editorialShelfBackgroundInfo.subtitleColor;
+ }
+ }
+ }
+ return shelf;
+}
+/**
+
+ * @param objectGraph The dependency graph for the App Store
+ * @param todayItem The flattened today item, of type EditorialItemGroup to create a shelf for
+ * @param pageContext The page context for the today page
+ * @returns The shelf for the given today item, or null if we cant create one
+ */
+export function todayShelfForEditorialItemGroup(objectGraph, todayItem, pageContext) {
+ var _a;
+ let isValidHeroStoryGroup = true;
+ const shelf = createTodayShelfWithItems(objectGraph, todayItem, pageContext, () => {
+ var _a, _b, _c, _d, _e, _f, _g;
+ const shelfItems = [];
+ const displayLimit = impressionDemotion.isImpressionDemotionAvailable(objectGraph)
+ ? (_a = asNumber(todayItem.data, "meta.personalizationData.displayEICount")) !== null && _a !== void 0 ? _a : 100
+ : 100;
+ let groupItems = asArrayOrEmpty(todayItem.data.meta, "associations.recommendations.data");
+ if (isSome(pageContext.recoImpressionData)) {
+ groupItems = impressionDemotion.personalizeDataItems(groupItems, pageContext.recoImpressionData, (_b = pageContext.pageInformation.recoMetricsData) !== null && _b !== void 0 ? _b : {});
+ }
+ const groupDisplayStyle = groupDisplayStyleFromData(objectGraph, todayItem.data);
+ let parsedGroupItemCount = 0;
+ for (const [index, groupItem] of groupItems.entries()) {
+ (_c = pageContext.pageInformation.iAdInfo) === null || _c === void 0 ? void 0 : _c.updateContainerId((_d = pageContext.pageInformation.iAdInfo) === null || _d === void 0 ? void 0 : _d.containerIdForSlotIndex((_e = pageContext.parsedCardCount) !== null && _e !== void 0 ? _e : 0));
+ updateCurrentRowMetricsDisplayStyle(objectGraph, pageContext, groupDisplayStyle, parsedGroupItemCount);
+ const cardConfig = createTodayCardConfiguration(objectGraph, groupItem, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), pageContext.currentRowMetricsDisplayStyle, groupDisplayStyle === models.GroupDisplayStyle.Hero && index === 0);
+ const shelfItem = createTodayCardForPageContext(objectGraph, pageContext, cardConfig, groupItem);
+ if (isSome(shelfItem)) {
+ shelfItems.push(shelfItem);
+ nextPosition(pageContext.locationTracker);
+ pageContext.parsedCardCount++;
+ incrementRowIndexIfNecessary(pageContext, groupDisplayStyle, parsedGroupItemCount);
+ parsedGroupItemCount++;
+ }
+ if (cardConfig.isHeroCard && isNothing(shelfItem)) {
+ if (["debug", "internal"].includes(objectGraph.client.buildType)) {
+ validation.unexpectedType("defaultValue", `Hero story group ${(_f = todayItem.data) === null || _f === void 0 ? void 0 : _f.id} must contain a valid hero card at index ${index}. Unable to parse card ${groupItem.id}.`, null);
+ }
+ isValidHeroStoryGroup = false;
+ }
+ if (index < groupItems.length - 1 &&
+ pageContext.adPlacementBehavior === models.AdPlacementBehavior.insertIntoShelf) {
+ // Attempt to insert the ad card after creating the next item in the group, but only
+ // if we're still **within** the story group, ads that fall at the beginning or end of
+ // the group are handled at the top level of the page parsing.
+ const adCard = createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, createTodayCardConfiguration(objectGraph, pageContext.adData, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), undefined, false));
+ if (isSome(adCard)) {
+ pageContext.parsedCardCount++;
+ incrementRowIndexIfNecessary(pageContext, models.GroupDisplayStyle.Standard, 0);
+ nextPosition(pageContext.locationTracker);
+ shelfItems.push(adCard);
+ }
+ }
+ // if the count is equal to the story limit. stop here and break.
+ if (shelfItems.length === displayLimit) {
+ break;
+ }
+ }
+ if (isValidHeroStoryGroup) {
+ // The number of items required to be a valid hero story group, this includes the hero
+ // card, and two additional cards to form a row
+ const heroStoryGroupRequiredItemCount = 3;
+ if (shelfItems.length !== heroStoryGroupRequiredItemCount) {
+ if (["debug", "internal"].includes(objectGraph.client.buildType)) {
+ validation.unexpectedType("defaultValue", `Hero story group ${(_g = todayItem.data) === null || _g === void 0 ? void 0 : _g.id} must contain exactly ${heroStoryGroupRequiredItemCount} items but only found ${shelfItems.length} items.`, null);
+ }
+ isValidHeroStoryGroup = false;
+ }
+ }
+ return shelfItems;
+ });
+ // Fallback to standard group display style if we had an issue creating a valid hero group
+ let groupDisplayStyle = groupDisplayStyleFromData(objectGraph, todayItem.data);
+ if (groupDisplayStyle === models.GroupDisplayStyle.Hero && !isValidHeroStoryGroup) {
+ groupDisplayStyle = models.GroupDisplayStyle.Standard;
+ }
+ shelf.contentsMetadata = {
+ type: "todaySection",
+ debugSectionTypeIndicatorColor: color.named("systemGreen"),
+ groupDisplayStyle: groupDisplayStyle,
+ };
+ // If this is not the first item in the module, attempt to set the background
+ if (!todayItem.isFirstItemInModule) {
+ const editorialShelfBackgroundInfo = todaySectionEditorialBackground(objectGraph, todayItem);
+ if (isSome(editorialShelfBackgroundInfo)) {
+ shelf.background = editorialShelfBackgroundInfo.shelfBackground;
+ if (isSome((_a = shelf.header) === null || _a === void 0 ? void 0 : _a.configuration)) {
+ shelf.header.configuration.eyebrowColor = editorialShelfBackgroundInfo.eyebrowColor;
+ shelf.header.configuration.titleColor = editorialShelfBackgroundInfo.titleColor;
+ shelf.header.configuration.subtitleColor = editorialShelfBackgroundInfo.subtitleColor;
+ }
+ }
+ else if (groupDisplayStyle === models.GroupDisplayStyle.Hero && Array.isArray(shelf.items)) {
+ shelf.background = todaySectionBackgroundForHeroDisplayStyle(shelf.items);
+ }
+ }
+ return shelf;
+}
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param todayItem The flattened today item, used to create the items in this shelf
+ * @param pageContext The page context for the today page
+ * @param itemProvider The function that will provide the items for this shelf
+ * @returns The shelf for the given today item
+ */
+function createTodayShelfWithItems(objectGraph, todayItem, pageContext, itemProvider) {
+ const shouldRecordShelfMetrics = todayItem.type === FlattenedTodayItemType.EditorialItemGroup;
+ const shelf = new models.Shelf("todayCard");
+ shelf.id = todayItem.data.id;
+ shelf.isHorizontal = false;
+ shelf.header = createTodayShelfHeaderForTodayItem(objectGraph, todayItem, pageContext);
+ if (shouldRecordShelfMetrics) {
+ const shelfMetricsOptions = {
+ id: shelf.id,
+ kind: "editorialItemGroup",
+ softwareType: null,
+ targetType: "swoosh",
+ title: sectionTitleFromTodayItem(objectGraph, todayItem, true),
+ pageInformation: pageContext.pageInformation,
+ locationTracker: pageContext.locationTracker,
+ idType: "its_id",
+ recoMetricsData: recoMetricsFromTodayItem(todayItem),
+ };
+ if (todayItem.type === FlattenedTodayItemType.EditorialItemGroup) {
+ shelfMetricsOptions["optimizationId"] = asString(todayItem.data, "meta.personalizationData.optimizationId");
+ shelfMetricsOptions["optimizationEntityId"] = asString(todayItem.data, "meta.personalizationData.optimizationEntityId");
+ }
+ addImpressionFields(objectGraph, shelf, shelfMetricsOptions);
+ pushContentLocation(objectGraph, shelfMetricsOptions, shelfMetricsOptions.title);
+ }
+ shelf.items = itemProvider();
+ shelf.isHidden = isNullOrEmpty(shelf.items);
+ if (shouldRecordShelfMetrics) {
+ popLocation(pageContext.locationTracker);
+ nextPosition(pageContext.locationTracker);
+ }
+ return shelf;
+}
+function createTodayShelfHeaderForTodayItem(objectGraph, todayItem, pageContext) {
+ var _a;
+ const shouldSuppressHeader = (_a = asBoolean(todayItem.data, "meta.personalizationData.suppressHeader")) !== null && _a !== void 0 ? _a : false;
+ if (shouldSuppressHeader) {
+ return null;
+ }
+ const shelfHeaderConfiguration = {
+ eyebrowImageColor: null,
+ titleImageColor: null,
+ includeSeparator: false,
+ };
+ const shelfHeader = {
+ eyebrow: sectionEyebrowFromTodayItem(objectGraph, todayItem),
+ eyebrowArtwork: sectionHeaderArtworkFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Eyebrow),
+ eyebrowArtworkType: sectionHeaderArtworkTypeFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Eyebrow),
+ title: sectionTitleFromTodayItem(objectGraph, todayItem),
+ titleArtwork: sectionHeaderArtworkFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Title),
+ titleArtworkType: sectionHeaderArtworkTypeFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Title),
+ subtitle: sectionSubtitleFromTodayItem(objectGraph, todayItem),
+ configuration: shelfHeaderConfiguration,
+ };
+ if (isSome(shelfHeader.eyebrow) || isSome(shelfHeader.title) || isSome(shelfHeader.subtitle)) {
+ return shelfHeader;
+ }
+ else {
+ return null;
+ }
+}
+/**
+ * Create a today card for a given today item in a page context. This handles requirements for ad replacement if needed
+ * @param objectGraph The dependency graph of the app store
+ * @param pageContext The context of the page so we can tell whether its time to insert an ad
+ * @param cardConfig The configuration for the today item
+ * @param todayItem The flattened today item that should be placed in the shelf or replaced with an ad card
+ * @returns The today card to insert in a shelf
+ */
+function createTodayCardForPageContext(objectGraph, pageContext, cardConfig, todayItem) {
+ var _a, _b;
+ let editorialItemTodayCard;
+ if (pageContext.adPlacementBehavior === models.AdPlacementBehavior.replaceOrganic &&
+ isDefinedNonNull(cardConfig.replaceIfAdPresent) &&
+ asBoolean(cardConfig.replaceIfAdPresent)) {
+ // the ad card should replace the organic
+ const adCard = createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, cardConfig);
+ if (isDefinedNonNullNonEmpty(adCard)) {
+ editorialItemTodayCard = adCard;
+ }
+ else {
+ editorialItemTodayCard = todayCardFromData(objectGraph, todayItem, cardConfig, pageContext);
+ }
+ }
+ else if (pageContext.adPlacementBehavior === models.AdPlacementBehavior.dropAd &&
+ isDefinedNonNull(cardConfig.replaceIfAdPresent) &&
+ !asBoolean(cardConfig.replaceIfAdPresent)) {
+ // the organic is not replaceable
+ editorialItemTodayCard = todayCardFromData(objectGraph, todayItem, cardConfig, pageContext);
+ if (isDefinedNonNullNonEmpty(pageContext.adData)) {
+ const recorder = pageContext.adIncidentRecorder;
+ (_a = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _a === void 0 ? void 0 : _a.setMissedOpportunity(objectGraph, "EDITORIALTAKEOVER", (_b = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _b === void 0 ? void 0 : _b.placementType);
+ }
+ }
+ else {
+ // we're using existing insertion logic that will insert an ad elsewhere. create a regular card for the flattened item
+ editorialItemTodayCard = todayCardFromData(objectGraph, todayItem, cardConfig, pageContext);
+ }
+ return editorialItemTodayCard;
+}
+// MARK: - Ads
+/**
+ * Try create a shelf to display an ad if we're currently at the correct spot in the feed. This is only used
+ * for single editorialItem shelves, and the end of an editorial item group, since these locations it does not
+ * make sense to include the ad within the shelf for that TodayItem.
+ * @param objectGraph The dependency graph of the app store
+ * @param pageContext The context of the page so we can tell whether its time to insert an ad
+ * @returns The card to insert at the ad slot if necessary
+ */
+export function createAdShelfForTodayPageContextIfNecessary(objectGraph, pageContext) {
+ let adShelf = null;
+ if (!isAdPlacementEnabled(objectGraph, "today") ||
+ isNothing(pageContext.adData) ||
+ pageContext.adPlacementBehavior !== models.AdPlacementBehavior.insertIntoShelf) {
+ return adShelf;
+ }
+ if (pageContext.adLocation !== pageContext.parsedCardCount) {
+ return adShelf;
+ }
+ adShelf = new models.Shelf("todayCard");
+ adShelf.id = pageContext.adData.id;
+ adShelf.isHorizontal = false;
+ adShelf.contentsMetadata = {
+ type: "todaySection",
+ debugSectionTypeIndicatorColor: color.named("systemBlue"),
+ groupDisplayStyle: models.GroupDisplayStyle.Standard,
+ };
+ const shelfItems = [];
+ /// Attempt to create an ad card before creating the next item in the group
+ const adCard = createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, createTodayCardConfiguration(objectGraph, pageContext.adData, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), undefined, false));
+ if (isSome(adCard)) {
+ pageContext.parsedCardCount++;
+ incrementRowIndexIfNecessary(pageContext, models.GroupDisplayStyle.Standard, 0);
+ nextPosition(pageContext.locationTracker);
+ shelfItems.push(adCard);
+ }
+ adShelf.items = shelfItems;
+ return isDefinedNonNullNonEmpty(adShelf.items) ? adShelf : null;
+}
+/**
+ * Try to create an ad card for the page context if the requirements are met
+ * @param objectGraph The dependency graph of the app store
+ * @param pageContext The context of the page so we can tell whether its time to insert an ad
+ * @param cardConfig The configuration of the card
+ * @returns The card to insert at the ad slot if necessary
+ */
+export function createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, cardConfig) {
+ var _a, _b, _c;
+ if (!isAdPlacementEnabled(objectGraph, "today")) {
+ return null;
+ }
+ // The ad should respect its slot, unless we're using reco logic for organic replacement. If we need to replace
+ // the organic, we will arbitrarily respect the reco flag to avoid edge cases with mismatching data
+ if (pageContext.adLocation !== pageContext.parsedCardCount &&
+ pageContext.adPlacementBehavior !== models.AdPlacementBehavior.replaceOrganic) {
+ return null;
+ }
+ (_a = pageContext.pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.updateContainerId((_b = pageContext.pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.containerIdForSlotIndex((_c = pageContext.parsedCardCount) !== null && _c !== void 0 ? _c : 0));
+ const adCard = createTodayAdCard(objectGraph, pageContext.adData, pageContext.adIncidentRecorder, cardConfig, pageContext);
+ if (isSome(adCard)) {
+ return adCard;
+ }
+ else {
+ return null;
+ }
+}
+// MARK: - Shelf Header Content
+/**
+ * @param objectGraph The dependency graph of the app store
+ * @param item The today item to look for the eyebrow in
+ * @returns The text displayed above the title on a section header
+ */
+export function sectionEyebrowFromTodayItem(objectGraph, item) {
+ const editorialClientParams = extractEditorialClientParams(objectGraph, item.data);
+ if (item.isFirstItemInModule || editorialClientParams.suppressHeaderBadge) {
+ return null;
+ }
+ let sectionBadge;
+ const editorialCard = editorialCardFromData(item.data);
+ if (hasAttributes(editorialCard)) {
+ sectionBadge = mediaAttributes.attributeAsString(editorialCard, "headerBadge");
+ }
+ if (isSome(sectionBadge)) {
+ return sectionBadge;
+ }
+ switch (item.data.type) {
+ case "editorial-items":
+ sectionBadge = attributeAsString(item.data, "headerBadge");
+ break;
+ case "editorial-item-groups":
+ sectionBadge = attributeAsString(item.data, ["editorialNotes", "badge"]);
+ break;
+ default:
+ break;
+ }
+ return sectionBadge;
+}
+/**
+ * @param objectGraph The dependency graph of the app store
+ * @param item The today item to look for the title in
+ * @param alwaysReturnTitle If true, the title will be returned even if it is the first item in a module
+ * @returns The title displayed above a section
+ */
+export function sectionTitleFromTodayItem(objectGraph, item, alwaysReturnTitle = false) {
+ const editorialClientParams = extractEditorialClientParams(objectGraph, item.data);
+ if ((item.isFirstItemInModule || editorialClientParams.suppressHeaderName) && !alwaysReturnTitle) {
+ return null;
+ }
+ let sectionTitle;
+ const editorialCard = editorialCardFromData(item.data);
+ if (hasAttributes(editorialCard)) {
+ sectionTitle = mediaAttributes.attributeAsString(editorialCard, "headerName");
+ }
+ if (isSome(sectionTitle)) {
+ return sectionTitle;
+ }
+ switch (item.data.type) {
+ case "editorial-items":
+ sectionTitle = attributeAsString(item.data, "headerName");
+ break;
+ case "editorial-item-groups":
+ sectionTitle = attributeAsString(item.data, ["editorialNotes", "name"]);
+ break;
+ default:
+ break;
+ }
+ return sectionTitle;
+}
+/**
+ * @param objectGraph The dependency graph of the app store
+ * @param item The today item to look for the subtitle in
+ * @returns The subtitle displayed below the title on a section header
+ */
+function sectionSubtitleFromTodayItem(objectGraph, item) {
+ const editorialClientParams = extractEditorialClientParams(objectGraph, item.data);
+ if (item.isFirstItemInModule || editorialClientParams.suppressHeaderTagline) {
+ return null;
+ }
+ let sectionSubtitle;
+ const editorialCard = editorialCardFromData(item.data);
+ if (hasAttributes(editorialCard)) {
+ sectionSubtitle = mediaAttributes.attributeAsString(editorialCard, "headerTagline");
+ }
+ if (isSome(sectionSubtitle)) {
+ return sectionSubtitle;
+ }
+ switch (item.data.type) {
+ case "editorial-items":
+ sectionSubtitle = attributeAsString(item.data, "headerTagline");
+ break;
+ case "editorial-item-groups":
+ sectionSubtitle = attributeAsString(item.data, ["editorialNotes", "tagline"]);
+ break;
+ default:
+ break;
+ }
+ return sectionSubtitle;
+}
+/**
+ * @param objectGraph The dependency graph of the app store
+ * @param item The today item to look for the subtitle in
+ * @returns The artwork displayed in the eyebrow of a today section.
+ */
+function sectionHeaderArtworkFromTodayItemForPlacement(objectGraph, item, placement) {
+ var _a;
+ const editorialClientParams = extractEditorialClientParams(objectGraph, item.data);
+ const headerContents = relationshipData(objectGraph, item.data, "header-contents");
+ const artworkBehavior = (_a = editorialClientParams.headerArtworkBehavior) !== null && _a !== void 0 ? _a : TodayHeaderArtworkBehavior.NoArtwork;
+ switch (placement) {
+ case TodaySectionHeaderArtworkPlacement.Eyebrow:
+ switch (artworkBehavior) {
+ case TodayHeaderArtworkBehavior.CategoryArtworkWithBadge:
+ return categoryArtworkFromData(objectGraph, headerContents);
+ default:
+ return null;
+ }
+ case TodaySectionHeaderArtworkPlacement.Title:
+ switch (artworkBehavior) {
+ case TodayHeaderArtworkBehavior.CategoryArtworkWithTitle:
+ return categoryArtworkFromData(objectGraph, headerContents);
+ case TodayHeaderArtworkBehavior.ContentArtworkWithTitle:
+ return iconFromData(objectGraph, headerContents, {
+ useCase: 1 /* ArtworkUseCase.LockupIconSmall */,
+ });
+ default:
+ return null;
+ }
+ default:
+ return null;
+ }
+}
+/**
+ * @param objectGraph The dependency graph of the app store
+ * @param item The today item to look for the subtitle in
+ * @returns The artwork type displayed in the eyebrow of a today section.
+ */
+function sectionHeaderArtworkTypeFromTodayItemForPlacement(objectGraph, item, placement) {
+ const headerContents = relationshipData(objectGraph, item.data, "header-contents");
+ const artworkBehavior = attributeAsString(item.data, [
+ "editorialClientParams",
+ "headerArtworkBehavior",
+ ]);
+ switch (placement) {
+ case TodaySectionHeaderArtworkPlacement.Eyebrow:
+ switch (artworkBehavior) {
+ case TodayHeaderArtworkBehavior.CategoryArtworkWithBadge:
+ const hasCategoryArtwork = isSome(categoryArtworkFromData(objectGraph, headerContents));
+ return hasCategoryArtwork ? models.ShelfHeaderArtworkType.Category : null;
+ default:
+ return null;
+ }
+ case TodaySectionHeaderArtworkPlacement.Title:
+ switch (artworkBehavior) {
+ case TodayHeaderArtworkBehavior.CategoryArtworkWithTitle:
+ const hasCategoryArtwork = isSome(categoryArtworkFromData(objectGraph, headerContents));
+ return hasCategoryArtwork ? models.ShelfHeaderArtworkType.Category : null;
+ case TodayHeaderArtworkBehavior.ContentArtworkWithTitle:
+ const hasIconArtwork = isSome(categoryArtworkFromData(objectGraph, headerContents));
+ return hasIconArtwork ? models.ShelfHeaderArtworkType.Icon : null;
+ default:
+ return null;
+ }
+ default:
+ return null;
+ }
+}
+/**
+ *
+ * @param objectGraph The dependency graph of the app store
+ * @param data The MAPI data for the related content for a today item
+ * @returns The artwork to use for the related content
+ */
+function categoryArtworkFromData(objectGraph, data) {
+ const artworkData = categoryArtworkData(objectGraph, data, false, false, false);
+ if (isNothing(artworkData)) {
+ return null;
+ }
+ const artwork = artworkFromApiArtwork(objectGraph, artworkData, {
+ useCase: 20 /* ArtworkUseCase.CategoryIcon */,
+ allowingTransparency: true,
+ cropCode: "sr",
+ });
+ return artwork;
+}
+/**
+ * @param objectGraph The dependency graph for the App Store
+ * @param todayItem The today item that contains the data for a editorial-item or editorial-item-group, this is used to look for
+ * editorialBackground, which can then be used to generate the section gradient background
+ * @returns The background to use for the section
+ */
+function todaySectionEditorialBackground(objectGraph, todayItem) {
+ const editorialBackground = mediaAttributes.attributeAsDictionary(todayItem.data, "editorialBackground", null);
+ const editorialBackgroundType = editorialBackground === null || editorialBackground === void 0 ? void 0 : editorialBackground["type"];
+ if (isNothing(editorialBackgroundType)) {
+ return null;
+ }
+ let backgroundInfo = null;
+ switch (editorialBackgroundType) {
+ case EditorialBackgroundType.LinearGradient:
+ const linearGradientData = asInterface(editorialBackground);
+ const colors = linearGradientData.stops.map((stop) => color.fromHex(stop.color));
+ const shelfBackground = {
+ type: "gradient",
+ colors: colors,
+ start: models.ShelfBackgroundGradientLocation.Top,
+ end: models.ShelfBackgroundGradientLocation.Bottom,
+ };
+ const isDark = color.isDarkColor(colors[0]);
+ const secondaryLabelLightColor = {
+ type: "rgb",
+ red: 60 / 255,
+ green: 60 / 255,
+ blue: 67 / 255,
+ alpha: 0.6,
+ };
+ const secondaryLabelDarkColor = {
+ type: "rgb",
+ red: 235.0 / 255,
+ green: 235.0 / 255,
+ blue: 245.0 / 255,
+ alpha: 0.6,
+ };
+ backgroundInfo = {
+ shelfBackground: shelfBackground,
+ eyebrowColor: isDark ? secondaryLabelDarkColor : secondaryLabelLightColor,
+ titleColor: isDark ? color.named("white") : color.named("black"),
+ subtitleColor: isDark ? secondaryLabelDarkColor : secondaryLabelLightColor,
+ };
+ break;
+ default:
+ backgroundInfo = null;
+ break;
+ }
+ return backgroundInfo;
+}
+/**
+ * @param todayCards The today cards that are contained in the section
+ * @returns The background to use for the section
+ */
+function todaySectionBackgroundForHeroDisplayStyle(todayCards) {
+ const backgroundColors = todayCards
+ .map((todayCard) => {
+ return todayCard.media.bestBackgroundColor();
+ })
+ .filter((backgroundColor) => isSome(backgroundColor));
+ let shelfBackground = null;
+ if (backgroundColors.length > 0 && backgroundColors.length <= 4 && backgroundColors.length === todayCards.length) {
+ switch (backgroundColors.length) {
+ case 1:
+ shelfBackground = {
+ type: "materialGradient",
+ colors: {
+ colorCount: "oneColor",
+ color: backgroundColors[0],
+ },
+ };
+ break;
+ case 2:
+ shelfBackground = {
+ type: "materialGradient",
+ colors: {
+ colorCount: "twoColor",
+ top: backgroundColors[0],
+ bottom: backgroundColors[1],
+ },
+ };
+ break;
+ case 3:
+ shelfBackground = {
+ type: "materialGradient",
+ colors: {
+ colorCount: "threeColor",
+ top: backgroundColors[0],
+ bottomLeading: backgroundColors[1],
+ bottomTrailing: backgroundColors[2],
+ },
+ };
+ break;
+ case 4:
+ shelfBackground = {
+ type: "materialGradient",
+ colors: {
+ colorCount: "fourColor",
+ topLeading: backgroundColors[0],
+ topTrailing: backgroundColors[1],
+ bottomLeading: backgroundColors[2],
+ bottomTrailing: backgroundColors[3],
+ },
+ };
+ break;
+ default:
+ break;
+ }
+ }
+ else {
+ shelfBackground = {
+ type: "color",
+ color: color.named("secondarySystemBackground"),
+ };
+ }
+ return shelfBackground;
+}
+// MARK: - Metrics
+/**
+ * Retrieves the recommendation metrics data from a FlattenedTodayItem object.
+ * @param todayItem - The FlattenedTodayItem object containing the module metadata.
+ * @returns The recommendation metrics data as a JSONData object.
+ */
+export function recoMetricsFromTodayItem(todayItem) {
+ var _a, _b;
+ if (isNothing(todayItem)) {
+ return {};
+ }
+ const recoMetricsData = (_a = asDictionary(todayItem.moduleMetadata, "meta.metrics")) !== null && _a !== void 0 ? _a : {};
+ const combinedRecoMetricsData = (_b = combinedRecoMetricsDataFromMetricsData(recoMetricsData, todayItem.moduleMetadata.onDevicePersonalizationProcessingType, null)) !== null && _b !== void 0 ? _b : {};
+ return combinedRecoMetricsData;
+}
+// MARK: - Debug Util
+/**
+ * Generate a todayCardPreview url that contains the entire original today feed.
+ * @param objectGraph The dependency graph of the app store
+ * @param todayItems The flattened today feed items
+ * @returns The url to add to the today page for debug purposes
+ */
+export function feedPreviewUrlFromFlattenedTodayItems(objectGraph, todayItems) {
+ switch (objectGraph.client.buildType) {
+ case "debug":
+ case "internal":
+ const feedPreviewUrl = new URL();
+ feedPreviewUrl.protocol = Protocol.https;
+ feedPreviewUrl.host = "apps.apple.com";
+ feedPreviewUrl.pathname = `/${Path.todayCardPreview}`;
+ const idsParamValues = [];
+ for (const item of todayItems) {
+ switch (item.type) {
+ case FlattenedTodayItemType.EditorialItem:
+ idsParamValues.push(item.data.id);
+ break;
+ case FlattenedTodayItemType.EditorialItemGroup:
+ const groupItems = asArrayOrEmpty(item.data.meta, "associations.recommendations.data");
+ idsParamValues.push(`${item.data.id}:[${groupItems.map((groupItem) => groupItem.id).join(",")}]`);
+ break;
+ default:
+ break;
+ }
+ }
+ feedPreviewUrl.param(Parameters.ids, idsParamValues.join(","));
+ feedPreviewUrl.param(Parameters.isTodayFeedPreview, "true");
+ return decodeURIComponent(feedPreviewUrl.build());
+ default:
+ return null;
+ }
+}
+/**
+ * Generate a todayCardPreview url that can display a single today card
+ * @param objectGraph The dependency graph of the app store
+ * @param todayCardId The id of the created today card
+ * @param cardConfig The card config for the created today card
+ * @returns The url to add to the today page for debug purposes
+ */
+export function todayCardPreviewUrlForTodayCard(objectGraph, todayCardId, cardConfig) {
+ if (isNothing(todayCardId) || !objectGraph.client.isiOS) {
+ return null;
+ }
+ switch (objectGraph.client.buildType) {
+ case "debug":
+ case "internal":
+ const feedPreviewUrl = new URL();
+ feedPreviewUrl.protocol = Protocol.https;
+ feedPreviewUrl.host = "apps.apple.com";
+ feedPreviewUrl.pathname = `/${Path.todayCardPreview}`;
+ feedPreviewUrl.param(Parameters.ids, `${todayCardId}`);
+ feedPreviewUrl.param(Parameters.isTodayFeedPreview, "true");
+ feedPreviewUrl.param(Parameters.isTodaySection, cardConfig.useOTDTextStyle ? "true" : "false");
+ return decodeURIComponent(feedPreviewUrl.build());
+ default:
+ return null;
+ }
+}
+//# sourceMappingURL=today-parse-util.js.map \ No newline at end of file