@universityofmaryland/web-feeds-library 1.3.5 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # University of Maryland Feeds Library
2
2
 
3
- [![Feeds Version](https://img.shields.io/badge/Feeds-v1.3.5-blue)](https://www.npmjs.com/package/@universityofmaryland/web-feeds-library)
3
+ [![Feeds Version](https://img.shields.io/badge/Feeds-v1.3.6-blue)](https://www.npmjs.com/package/@universityofmaryland/web-feeds-library)
4
4
 
5
5
  Dynamic content feed components for displaying University of Maryland news, events, and academic information with automatic updates, caching, and brand-compliant styling.
6
6
 
@@ -1 +1 @@
1
- {"version":3,"file":"createBaseFeed.d.ts","sourceRoot":"","sources":["../../../source/factory/core/createBaseFeed.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAsB,MAAM,SAAS,CAAC;AAgDhF,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,EACpD,MAAM,EAAE,cAAc,CAAC,KAAK,EAAE,UAAU,CAAC,GACxC,iBAAiB,CAsInB"}
1
+ {"version":3,"file":"createBaseFeed.d.ts","sourceRoot":"","sources":["../../../source/factory/core/createBaseFeed.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAsB,MAAM,SAAS,CAAC;AAgDhF,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,EACpD,MAAM,EAAE,cAAc,CAAC,KAAK,EAAE,UAAU,CAAC,GACxC,iBAAiB,CAuInB"}
@@ -13,7 +13,6 @@ function createBaseFeed(config) {
13
13
  isTransparent = false,
14
14
  isOverlay = false,
15
15
  isAligned = false,
16
- isMediaTrained = null,
17
16
  cardType,
18
17
  numberOfColumnsToShow = 1,
19
18
  numberOfRowsToStart,
@@ -26,7 +25,9 @@ function createBaseFeed(config) {
26
25
  displayStrategy,
27
26
  layoutStrategy,
28
27
  noResultsConfig,
29
- imageConfig
28
+ imageConfig,
29
+ // Collect strategy-specific props (e.g. isUnion, isMediaTrained)
30
+ ...strategyProps
30
31
  } = config;
31
32
  const container = document.createElement("div");
32
33
  const loading = new LoadingState({ isThemeDark });
@@ -64,8 +65,8 @@ function createBaseFeed(config) {
64
65
  numberOfColumnsToShow,
65
66
  numberOfRowsToStart,
66
67
  isLazyLoad,
67
- isMediaTrained,
68
- getOffset: helpers.getOffset
68
+ getOffset: helpers.getOffset,
69
+ ...strategyProps
69
70
  };
70
71
  const lazyLoadCallbackRef = {
71
72
  current: void 0
@@ -1 +1 @@
1
- {"version":3,"file":"createBaseFeed.js","sources":["../../../source/factory/core/createBaseFeed.ts"],"sourcesContent":["/**\n * Base Feed Factory\n *\n * Creates a complete feed with initialization, data fetching, and display logic.\n * Uses strategy pattern to allow customization of fetch, display, and layout.\n *\n * @module factory/core/createBaseFeed\n */\n\nimport { LoadingState } from '../../states';\nimport {\n createFeedHelpers,\n createDisplayHandlers,\n createFetchHandlers,\n} from '../helpers';\nimport { BaseFeedConfig, FeedFactoryResult, CardMappingOptions } from './types';\n\n/**\n * Create a base feed using the factory pattern\n *\n * This factory eliminates ~70% of boilerplate code by handling:\n * - Feed initialization (container, loading, state)\n * - Strategy composition (fetch, display, layout)\n * - Lifecycle management (start, load more, no results)\n * - Shadow DOM support\n * - Event system\n *\n * @template TData - The type of entry data from the API\n * @template TVariables - The type of API variables\n *\n * @param config - Configuration for the feed\n * @returns ElementModel with feed element and styles\n *\n * @example\n * ```typescript\n * // Simple grid feed\n * const eventsFeed = createBaseFeed({\n * token: 'my-token',\n * isThemeDark: false,\n * numberOfColumnsToShow: 3,\n * numberOfRowsToStart: 2,\n * isLazyLoad: true,\n * fetchStrategy: eventsFetchStrategy,\n * displayStrategy: eventsDisplayStrategy,\n * layoutStrategy: gridGapLayout,\n * imageConfig: (entry) => {\n * const imageUrl = entry.image?.[0]?.url;\n * const altText = entry.image?.[0]?.altText;\n *\n * if (!imageUrl || !altText) return null;\n *\n * return {\n * imageUrl: imageUrl,\n * altText: altText,\n * linkUrl: entry.url,\n * };\n * },\n * });\n *\n * // Use the feed\n * document.body.appendChild(eventsFeed.element);\n * ```\n */\nexport function createBaseFeed<TData, TVariables = any>(\n config: BaseFeedConfig<TData, TVariables>,\n): FeedFactoryResult {\n const {\n token,\n isThemeDark = false,\n isTransparent = false,\n isOverlay = false,\n isAligned = false,\n isMediaTrained = null,\n cardType,\n numberOfColumnsToShow = 1,\n numberOfRowsToStart,\n isLazyLoad = false,\n enableCategoryFallback = false,\n categories,\n ids,\n entriesToRemove,\n fetchStrategy,\n displayStrategy,\n layoutStrategy,\n noResultsConfig,\n imageConfig,\n } = config;\n\n const container = document.createElement('div');\n const loading = new LoadingState({ isThemeDark });\n let styles = loading.styles;\n\n // Create helpers for state management\n const helpers = createFeedHelpers({\n container,\n initialStyles: styles,\n });\n\n // Override setStyles to also update the local styles variable\n const originalSetStyles = helpers.setStyles;\n helpers.setStyles = (additionalStyles: string) => {\n styles += additionalStyles;\n originalSetStyles(additionalStyles);\n };\n\n // Shadow root support\n let shadowRoot: ShadowRoot | null = null;\n const shadowRootCallback = (shadow: ShadowRoot) => {\n shadowRoot = shadow;\n\n helpers.setShadowRoot(shadow);\n };\n\n const cardMappingOptions: CardMappingOptions = {\n isThemeDark,\n isTransparent,\n isOverlay,\n isAligned,\n imageConfig,\n ...(cardType && { cardType }),\n };\n\n const layoutElement = layoutStrategy.create({\n columns: numberOfColumnsToShow as 2 | 3 | 4,\n isThemeDark,\n });\n helpers.setStyles(layoutElement.styles);\n\n const baseProps = {\n token,\n categories,\n ids,\n entriesToRemove,\n numberOfColumnsToShow,\n numberOfRowsToStart,\n isLazyLoad,\n isMediaTrained,\n getOffset: helpers.getOffset,\n };\n\n // Create a mutable reference for lazy load callback\n // This allows us to set it after fetchHandlers are created\n const lazyLoadCallbackRef = {\n current: undefined as (() => Promise<void>) | undefined,\n };\n\n // Create display handlers with callback reference\n const displayHandlers = createDisplayHandlers({\n displayStrategy,\n layoutStrategy,\n helpers,\n cardMappingOptions,\n isLazyLoad,\n numberOfColumnsToShow,\n numberOfRowsToStart,\n noResultsConfig: {\n ...noResultsConfig,\n isThemeDark, // Ensure theme is passed to EmptyState\n },\n // Pass a function that calls the current callback\n lazyLoadCallback: isLazyLoad\n ? async () => {\n if (lazyLoadCallbackRef.current) {\n await lazyLoadCallbackRef.current();\n }\n }\n : undefined,\n });\n\n // Create fetch handlers\n const fetchHandlers = createFetchHandlers({\n fetchStrategy,\n helpers,\n baseProps,\n displayHandlers,\n layoutElement,\n isThemeDark,\n enableCategoryFallback,\n });\n\n // Set the lazy load callback reference now that fetch handlers exist\n lazyLoadCallbackRef.current = fetchHandlers.loadMore;\n\n // Append loading indicator to container\n container.appendChild(loading.element);\n\n // Start fetching data\n fetchHandlers.start();\n\n // Return feed with getter for styles to ensure dynamic access\n return {\n element: container,\n get styles() {\n return styles;\n },\n events: {\n callback: shadowRootCallback,\n },\n };\n}\n"],"names":[],"mappings":";;;;;;;;AA+DO,SAAS,eACd,QACmB;AACnB,QAAM;AAAA,IACJ;AAAA,IACA,cAAc;AAAA,IACd,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB;AAAA,IACA,wBAAwB;AAAA,IACxB;AAAA,IACA,aAAa;AAAA,IACb,yBAAyB;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,IACE;AAEJ,QAAM,YAAY,SAAS,cAAc,KAAK;AAC9C,QAAM,UAAU,IAAI,aAAa,EAAE,aAAa;AAChD,MAAI,SAAS,QAAQ;AAGrB,QAAM,UAAU,kBAAkB;AAAA,IAChC;AAAA,IACA,eAAe;AAAA,EAAA,CAChB;AAGD,QAAM,oBAAoB,QAAQ;AAClC,UAAQ,YAAY,CAAC,qBAA6B;AAChD,cAAU;AACV,sBAAkB,gBAAgB;AAAA,EACpC;AAIA,QAAM,qBAAqB,CAAC,WAAuB;AAGjD,YAAQ,cAAc,MAAM;AAAA,EAC9B;AAEA,QAAM,qBAAyC;AAAA,IAC7C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAI,YAAY,EAAE,SAAA;AAAA,EAAS;AAG7B,QAAM,gBAAgB,eAAe,OAAO;AAAA,IAC1C,SAAS;AAAA,IACT;AAAA,EAAA,CACD;AACD,UAAQ,UAAU,cAAc,MAAM;AAEtC,QAAM,YAAY;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,QAAQ;AAAA,EAAA;AAKrB,QAAM,sBAAsB;AAAA,IAC1B,SAAS;AAAA,EAAA;AAIX,QAAM,kBAAkB,sBAAsB;AAAA,IAC5C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,MACf,GAAG;AAAA,MACH;AAAA;AAAA,IAAA;AAAA;AAAA,IAGF,kBAAkB,aACd,YAAY;AACV,UAAI,oBAAoB,SAAS;AAC/B,cAAM,oBAAoB,QAAA;AAAA,MAC5B;AAAA,IACF,IACA;AAAA,EAAA,CACL;AAGD,QAAM,gBAAgB,oBAAoB;AAAA,IACxC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,CACD;AAGD,sBAAoB,UAAU,cAAc;AAG5C,YAAU,YAAY,QAAQ,OAAO;AAGrC,gBAAc,MAAA;AAGd,SAAO;AAAA,IACL,SAAS;AAAA,IACT,IAAI,SAAS;AACX,aAAO;AAAA,IACT;AAAA,IACA,QAAQ;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,EACZ;AAEJ;"}
1
+ {"version":3,"file":"createBaseFeed.js","sources":["../../../source/factory/core/createBaseFeed.ts"],"sourcesContent":["/**\n * Base Feed Factory\n *\n * Creates a complete feed with initialization, data fetching, and display logic.\n * Uses strategy pattern to allow customization of fetch, display, and layout.\n *\n * @module factory/core/createBaseFeed\n */\n\nimport { LoadingState } from '../../states';\nimport {\n createFeedHelpers,\n createDisplayHandlers,\n createFetchHandlers,\n} from '../helpers';\nimport { BaseFeedConfig, FeedFactoryResult, CardMappingOptions } from './types';\n\n/**\n * Create a base feed using the factory pattern\n *\n * This factory eliminates ~70% of boilerplate code by handling:\n * - Feed initialization (container, loading, state)\n * - Strategy composition (fetch, display, layout)\n * - Lifecycle management (start, load more, no results)\n * - Shadow DOM support\n * - Event system\n *\n * @template TData - The type of entry data from the API\n * @template TVariables - The type of API variables\n *\n * @param config - Configuration for the feed\n * @returns ElementModel with feed element and styles\n *\n * @example\n * ```typescript\n * // Simple grid feed\n * const eventsFeed = createBaseFeed({\n * token: 'my-token',\n * isThemeDark: false,\n * numberOfColumnsToShow: 3,\n * numberOfRowsToStart: 2,\n * isLazyLoad: true,\n * fetchStrategy: eventsFetchStrategy,\n * displayStrategy: eventsDisplayStrategy,\n * layoutStrategy: gridGapLayout,\n * imageConfig: (entry) => {\n * const imageUrl = entry.image?.[0]?.url;\n * const altText = entry.image?.[0]?.altText;\n *\n * if (!imageUrl || !altText) return null;\n *\n * return {\n * imageUrl: imageUrl,\n * altText: altText,\n * linkUrl: entry.url,\n * };\n * },\n * });\n *\n * // Use the feed\n * document.body.appendChild(eventsFeed.element);\n * ```\n */\nexport function createBaseFeed<TData, TVariables = any>(\n config: BaseFeedConfig<TData, TVariables>,\n): FeedFactoryResult {\n const {\n token,\n isThemeDark = false,\n isTransparent = false,\n isOverlay = false,\n isAligned = false,\n cardType,\n numberOfColumnsToShow = 1,\n numberOfRowsToStart,\n isLazyLoad = false,\n enableCategoryFallback = false,\n categories,\n ids,\n entriesToRemove,\n fetchStrategy,\n displayStrategy,\n layoutStrategy,\n noResultsConfig,\n imageConfig,\n // Collect strategy-specific props (e.g. isUnion, isMediaTrained)\n ...strategyProps\n } = config;\n\n const container = document.createElement('div');\n const loading = new LoadingState({ isThemeDark });\n let styles = loading.styles;\n\n // Create helpers for state management\n const helpers = createFeedHelpers({\n container,\n initialStyles: styles,\n });\n\n // Override setStyles to also update the local styles variable\n const originalSetStyles = helpers.setStyles;\n helpers.setStyles = (additionalStyles: string) => {\n styles += additionalStyles;\n originalSetStyles(additionalStyles);\n };\n\n // Shadow root support\n let shadowRoot: ShadowRoot | null = null;\n const shadowRootCallback = (shadow: ShadowRoot) => {\n shadowRoot = shadow;\n\n helpers.setShadowRoot(shadow);\n };\n\n const cardMappingOptions: CardMappingOptions = {\n isThemeDark,\n isTransparent,\n isOverlay,\n isAligned,\n imageConfig,\n ...(cardType && { cardType }),\n };\n\n const layoutElement = layoutStrategy.create({\n columns: numberOfColumnsToShow as 2 | 3 | 4,\n isThemeDark,\n });\n helpers.setStyles(layoutElement.styles);\n\n const baseProps = {\n token,\n categories,\n ids,\n entriesToRemove,\n numberOfColumnsToShow,\n numberOfRowsToStart,\n isLazyLoad,\n getOffset: helpers.getOffset,\n ...strategyProps,\n };\n\n // Create a mutable reference for lazy load callback\n // This allows us to set it after fetchHandlers are created\n const lazyLoadCallbackRef = {\n current: undefined as (() => Promise<void>) | undefined,\n };\n\n // Create display handlers with callback reference\n const displayHandlers = createDisplayHandlers({\n displayStrategy,\n layoutStrategy,\n helpers,\n cardMappingOptions,\n isLazyLoad,\n numberOfColumnsToShow,\n numberOfRowsToStart,\n noResultsConfig: {\n ...noResultsConfig,\n isThemeDark, // Ensure theme is passed to EmptyState\n },\n // Pass a function that calls the current callback\n lazyLoadCallback: isLazyLoad\n ? async () => {\n if (lazyLoadCallbackRef.current) {\n await lazyLoadCallbackRef.current();\n }\n }\n : undefined,\n });\n\n // Create fetch handlers\n const fetchHandlers = createFetchHandlers({\n fetchStrategy,\n helpers,\n baseProps,\n displayHandlers,\n layoutElement,\n isThemeDark,\n enableCategoryFallback,\n });\n\n // Set the lazy load callback reference now that fetch handlers exist\n lazyLoadCallbackRef.current = fetchHandlers.loadMore;\n\n // Append loading indicator to container\n container.appendChild(loading.element);\n\n // Start fetching data\n fetchHandlers.start();\n\n // Return feed with getter for styles to ensure dynamic access\n return {\n element: container,\n get styles() {\n return styles;\n },\n events: {\n callback: shadowRootCallback,\n },\n };\n}\n"],"names":[],"mappings":";;;;;;;;AA+DO,SAAS,eACd,QACmB;AACnB,QAAM;AAAA,IACJ;AAAA,IACA,cAAc;AAAA,IACd,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ;AAAA,IACA,wBAAwB;AAAA,IACxB;AAAA,IACA,aAAa;AAAA,IACb,yBAAyB;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA,GAAG;AAAA,EAAA,IACD;AAEJ,QAAM,YAAY,SAAS,cAAc,KAAK;AAC9C,QAAM,UAAU,IAAI,aAAa,EAAE,aAAa;AAChD,MAAI,SAAS,QAAQ;AAGrB,QAAM,UAAU,kBAAkB;AAAA,IAChC;AAAA,IACA,eAAe;AAAA,EAAA,CAChB;AAGD,QAAM,oBAAoB,QAAQ;AAClC,UAAQ,YAAY,CAAC,qBAA6B;AAChD,cAAU;AACV,sBAAkB,gBAAgB;AAAA,EACpC;AAIA,QAAM,qBAAqB,CAAC,WAAuB;AAGjD,YAAQ,cAAc,MAAM;AAAA,EAC9B;AAEA,QAAM,qBAAyC;AAAA,IAC7C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAI,YAAY,EAAE,SAAA;AAAA,EAAS;AAG7B,QAAM,gBAAgB,eAAe,OAAO;AAAA,IAC1C,SAAS;AAAA,IACT;AAAA,EAAA,CACD;AACD,UAAQ,UAAU,cAAc,MAAM;AAEtC,QAAM,YAAY;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,QAAQ;AAAA,IACnB,GAAG;AAAA,EAAA;AAKL,QAAM,sBAAsB;AAAA,IAC1B,SAAS;AAAA,EAAA;AAIX,QAAM,kBAAkB,sBAAsB;AAAA,IAC5C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,MACf,GAAG;AAAA,MACH;AAAA;AAAA,IAAA;AAAA;AAAA,IAGF,kBAAkB,aACd,YAAY;AACV,UAAI,oBAAoB,SAAS;AAC/B,cAAM,oBAAoB,QAAA;AAAA,MAC5B;AAAA,IACF,IACA;AAAA,EAAA,CACL;AAGD,QAAM,gBAAgB,oBAAoB;AAAA,IACxC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,CACD;AAGD,sBAAoB,UAAU,cAAc;AAG5C,YAAU,YAAY,QAAQ,OAAO;AAGrC,gBAAc,MAAA;AAGd,SAAO;AAAA,IACL,SAAS;AAAA,IACT,IAAI,SAAS;AACX,aAAO;AAAA,IACT;AAAA,IACA,QAAQ;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,EACZ;AAEJ;"}
@@ -64,7 +64,6 @@ export interface BaseFeedConfig<TData, TVariables = any> {
64
64
  numberOfRowsToStart: number;
65
65
  isLazyLoad?: boolean;
66
66
  enableCategoryFallback?: boolean;
67
- isMediaTrained?: boolean | null;
68
67
  categories?: string[];
69
68
  ids?: string[];
70
69
  entriesToRemove?: Array<string | number>;
@@ -74,6 +73,7 @@ export interface BaseFeedConfig<TData, TVariables = any> {
74
73
  noResultsConfig?: NoResultsConfig;
75
74
  imageConfig?: (entry: TData) => ImageConfig | null;
76
75
  getOffset?: () => number;
76
+ [key: string]: any;
77
77
  }
78
78
  export interface DisplayResultsProps<TData> {
79
79
  feedData: TData[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../source/factory/core/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../source/factory/core/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"bio.d.ts","sourceRoot":"","sources":["../../../source/feeds/experts/bio.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AA8MjD,eAAO,MAAM,UAAU,GAAI,OAAO,QAAQ,KAAG,YAqE5C,CAAC"}
1
+ {"version":3,"file":"bio.d.ts","sourceRoot":"","sources":["../../../source/feeds/experts/bio.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAqNjD,eAAO,MAAM,UAAU,GAAI,OAAO,QAAQ,KAAG,YAqE5C,CAAC"}
@@ -81,7 +81,14 @@ const renderSuccess = async (container, expert, state, isThemeDark) => {
81
81
  const announcer = createSuccessAnnouncer(expert);
82
82
  const children = [bioElement.element, announcer.getElement()];
83
83
  children.forEach((child) => container.appendChild(child));
84
- state.addStyles(bioElement.styles);
84
+ state.addStyles(
85
+ `
86
+ a:hover, a:focus {
87
+ text-decoration: underline;
88
+ }
89
+ ${bioElement.styles}
90
+ `
91
+ );
85
92
  await state.updateShadowStyles();
86
93
  };
87
94
  const logError = (errorType, expertId, error) => {
@@ -1 +1 @@
1
- {"version":3,"file":"bio.js","sources":["../../../source/feeds/experts/bio.ts"],"sourcesContent":["/**\n * Expert Bio Feed (Refactored with Element Builder)\n *\n * Displays a single expert's profile with summary.\n * Uses element builder pattern for clean, declarative construction.\n *\n * @module feeds/experts/bio\n */\n\nimport { ElementBuilder } from '@universityofmaryland/web-builder-library';\nimport { person } from '@universityofmaryland/web-elements-library/composite';\nimport { LoadingState, Announcer } from '../../states';\nimport { expertsFetchStrategy } from '../../strategies/fetch/experts';\nimport {\n mapExpertToBioProps,\n buildFullName,\n} from '../../strategies/display/experts';\nimport { styles as styleUtilities } from '../../helpers';\nimport { type ExpertEntry } from 'types/data';\nimport { type BioProps } from './_types';\nimport { type ElementModel } from '../../_types';\n\n// ============================================================================\n// PURE HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Create base props for fetch strategy\n *\n * @param token - API authentication token\n * @param expertId - Expert ID to fetch\n * @returns Base props object for strategy's composeApiVariables\n */\nconst createFetchProps = (token: string, expertId: string) => ({\n token,\n limit: 1,\n offset: 0,\n ids: [expertId],\n});\n\n/**\n * Create accessibility announcer for loaded expert\n *\n * @param expert - Expert entry\n * @returns Announcer element model\n */\nconst createSuccessAnnouncer = (expert: ExpertEntry): Announcer => {\n const fullName = buildFullName(expert);\n const message = `Loaded profile for ${fullName}`;\n return new Announcer({ message });\n};\n\n// ============================================================================\n// STATE MANAGER CLASS\n// ============================================================================\n\n/**\n * Manages bio feed state and shadow DOM synchronization\n *\n * Encapsulates style accumulation and shadow DOM updates.\n * Provides immutable-style API for adding styles.\n */\nclass BioFeedState {\n private stylesArray: string[] = [];\n private shadowRoot: ShadowRoot | null = null;\n\n /**\n * Initialize state with initial styles\n *\n * @param initialStyles - Initial CSS styles\n */\n constructor(initialStyles: string) {\n this.stylesArray.push(initialStyles);\n }\n\n /**\n * Add styles to the accumulated styles\n *\n * @param styles - CSS styles to add\n */\n addStyles(styles: string): void {\n this.stylesArray.push(styles);\n }\n\n /**\n * Set shadow root reference for style updates\n *\n * @param shadow - Shadow root element\n */\n setShadowRoot(shadow: ShadowRoot): void {\n this.shadowRoot = shadow;\n }\n\n /**\n * Update shadow DOM styles\n *\n * @returns Promise that resolves when styles are updated\n */\n async updateShadowStyles(): Promise<void> {\n if (!this.shadowRoot) return;\n await styleUtilities.setShadowStyles({\n shadowRoot: this.shadowRoot,\n styles: this.getStyles(),\n });\n }\n\n /**\n * Get accumulated styles as single string\n *\n * @returns Combined CSS styles\n */\n getStyles(): string {\n return this.stylesArray.join('\\n');\n }\n\n /**\n * Get shadow root callback for events\n *\n * @returns Callback function for shadow root\n */\n getShadowCallback(): (shadow: ShadowRoot) => void {\n return (shadow) => this.setShadowRoot(shadow);\n }\n}\n\n// ============================================================================\n// RENDERING FUNCTIONS\n// ============================================================================\n\n/**\n * Render successful bio with expert data\n *\n * @param container - Container element to render into\n * @param expert - Expert entry data\n * @param state - State manager instance\n * @param isThemeDark - Dark theme flag\n * @returns Promise that resolves when rendering is complete\n */\nconst renderSuccess = async (\n container: HTMLElement,\n expert: ExpertEntry,\n state: BioFeedState,\n isThemeDark: boolean,\n): Promise<void> => {\n const bioProps = mapExpertToBioProps(expert, 'small', isThemeDark);\n const bioElement = person.bio.small(bioProps);\n const announcer = createSuccessAnnouncer(expert);\n const children = [bioElement.element, announcer.getElement()];\n\n children.forEach((child) => container.appendChild(child));\n\n state.addStyles(bioElement.styles);\n await state.updateShadowStyles();\n};\n\n/**\n * Error types for expert bio feed\n */\ntype BioErrorType = 'not_found' | 'graphql_error' | 'invalid_request';\n\n/**\n * Log error to console with specific warning message\n *\n * @param errorType - Type of error that occurred\n * @param expertId - Expert ID that was requested\n * @param error - Optional error object for GraphQL errors\n */\nconst logError = (\n errorType: BioErrorType,\n expertId: string,\n error?: any,\n): void => {\n switch (errorType) {\n case 'not_found':\n console.warn(\n `[Expert Bio Feed] No expert found with ID \"${expertId}\". ` +\n `Please verify the expert ID is correct and the expert exists in the system.`,\n );\n break;\n case 'graphql_error':\n console.warn(\n `[Expert Bio Feed] GraphQL error occurred while fetching expert \"${expertId}\". ` +\n `Check network connection and API availability.`,\n error,\n );\n break;\n case 'invalid_request':\n console.warn(\n `[Expert Bio Feed] Invalid request for expert \"${expertId}\". ` +\n `Ensure both data-token and data-id attributes are provided.`,\n );\n break;\n }\n};\n\n// ============================================================================\n// MAIN EXPORT\n// ============================================================================\n\n/**\n * Create an expert bio feed\n *\n * Fetches and displays a single expert's bio with summary.\n * Uses element builder pattern for clean construction.\n *\n * @param props - Feed configuration\n * @returns ElementModel with bio element, styles, and events\n *\n * @example\n * ```typescript\n * const bio = expertBio({\n * token: 'my-token',\n * expertId: 'john-doe',\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With dark theme\n * const bio = expertBio({\n * token: 'my-token',\n * expertId: 'jane-smith',\n * isThemeDark: true,\n * });\n * ```\n */\nexport const expertsBio = (props: BioProps): ElementModel => {\n const { token, expertId, isThemeDark = false } = props;\n\n // Create container using ElementBuilder\n const containerBuilder = new ElementBuilder('div').withClassName(\n 'expert-bio-feed',\n );\n\n // Get element for manipulation (non-destructive)\n const container = containerBuilder.getElement();\n\n // Validate required expertId\n if (!expertId) {\n logError('invalid_request', 'unknown');\n const model = containerBuilder.build();\n return {\n element: model.element,\n styles: '',\n events: {},\n };\n }\n\n // Store validated expertId for use in async function\n const validExpertId = expertId;\n\n // Initialize state management\n const loading = new LoadingState({ isThemeDark });\n const state = new BioFeedState(loading.styles);\n\n /**\n * Fetch expert data and render\n */\n const initialize = async (): Promise<void> => {\n loading.show(container);\n\n try {\n const fetchProps = createFetchProps(token, validExpertId);\n const variables = expertsFetchStrategy.composeApiVariables(fetchProps);\n const entries = await expertsFetchStrategy.fetchEntries(variables);\n\n loading.hide();\n\n if (!entries || entries.length === 0) {\n logError('not_found', validExpertId);\n return;\n }\n\n await renderSuccess(container, entries[0], state, isThemeDark);\n } catch (error) {\n loading.hide();\n logError('graphql_error', validExpertId, error);\n }\n };\n\n // Start initialization\n initialize();\n\n // Build and return element model\n const model = containerBuilder.build();\n\n return {\n element: model.element,\n get styles() {\n return state.getStyles();\n },\n events: {\n callback: state.getShadowCallback(),\n },\n };\n};\n"],"names":["styleUtilities.setShadowStyles","model"],"mappings":"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"}
1
+ {"version":3,"file":"bio.js","sources":["../../../source/feeds/experts/bio.ts"],"sourcesContent":["/**\n * Expert Bio Feed (Refactored with Element Builder)\n *\n * Displays a single expert's profile with summary.\n * Uses element builder pattern for clean, declarative construction.\n *\n * @module feeds/experts/bio\n */\n\nimport { ElementBuilder } from '@universityofmaryland/web-builder-library';\nimport { person } from '@universityofmaryland/web-elements-library/composite';\nimport { LoadingState, Announcer } from '../../states';\nimport { expertsFetchStrategy } from '../../strategies/fetch/experts';\nimport {\n mapExpertToBioProps,\n buildFullName,\n} from '../../strategies/display/experts';\nimport { styles as styleUtilities } from '../../helpers';\nimport { type ExpertEntry } from 'types/data';\nimport { type BioProps } from './_types';\nimport { type ElementModel } from '../../_types';\n\n// ============================================================================\n// PURE HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Create base props for fetch strategy\n *\n * @param token - API authentication token\n * @param expertId - Expert ID to fetch\n * @returns Base props object for strategy's composeApiVariables\n */\nconst createFetchProps = (token: string, expertId: string) => ({\n token,\n limit: 1,\n offset: 0,\n ids: [expertId],\n});\n\n/**\n * Create accessibility announcer for loaded expert\n *\n * @param expert - Expert entry\n * @returns Announcer element model\n */\nconst createSuccessAnnouncer = (expert: ExpertEntry): Announcer => {\n const fullName = buildFullName(expert);\n const message = `Loaded profile for ${fullName}`;\n return new Announcer({ message });\n};\n\n// ============================================================================\n// STATE MANAGER CLASS\n// ============================================================================\n\n/**\n * Manages bio feed state and shadow DOM synchronization\n *\n * Encapsulates style accumulation and shadow DOM updates.\n * Provides immutable-style API for adding styles.\n */\nclass BioFeedState {\n private stylesArray: string[] = [];\n private shadowRoot: ShadowRoot | null = null;\n\n /**\n * Initialize state with initial styles\n *\n * @param initialStyles - Initial CSS styles\n */\n constructor(initialStyles: string) {\n this.stylesArray.push(initialStyles);\n }\n\n /**\n * Add styles to the accumulated styles\n *\n * @param styles - CSS styles to add\n */\n addStyles(styles: string): void {\n this.stylesArray.push(styles);\n }\n\n /**\n * Set shadow root reference for style updates\n *\n * @param shadow - Shadow root element\n */\n setShadowRoot(shadow: ShadowRoot): void {\n this.shadowRoot = shadow;\n }\n\n /**\n * Update shadow DOM styles\n *\n * @returns Promise that resolves when styles are updated\n */\n async updateShadowStyles(): Promise<void> {\n if (!this.shadowRoot) return;\n await styleUtilities.setShadowStyles({\n shadowRoot: this.shadowRoot,\n styles: this.getStyles(),\n });\n }\n\n /**\n * Get accumulated styles as single string\n *\n * @returns Combined CSS styles\n */\n getStyles(): string {\n return this.stylesArray.join('\\n');\n }\n\n /**\n * Get shadow root callback for events\n *\n * @returns Callback function for shadow root\n */\n getShadowCallback(): (shadow: ShadowRoot) => void {\n return (shadow) => this.setShadowRoot(shadow);\n }\n}\n\n// ============================================================================\n// RENDERING FUNCTIONS\n// ============================================================================\n\n/**\n * Render successful bio with expert data\n *\n * @param container - Container element to render into\n * @param expert - Expert entry data\n * @param state - State manager instance\n * @param isThemeDark - Dark theme flag\n * @returns Promise that resolves when rendering is complete\n */\nconst renderSuccess = async (\n container: HTMLElement,\n expert: ExpertEntry,\n state: BioFeedState,\n isThemeDark: boolean,\n): Promise<void> => {\n const bioProps = mapExpertToBioProps(expert, 'small', isThemeDark);\n const bioElement = person.bio.small(bioProps);\n const announcer = createSuccessAnnouncer(expert);\n const children = [bioElement.element, announcer.getElement()];\n\n children.forEach((child) => container.appendChild(child));\n\n state.addStyles(\n `\n a:hover, a:focus {\n text-decoration: underline;\n }\n ${bioElement.styles}\n `,\n );\n await state.updateShadowStyles();\n};\n\n/**\n * Error types for expert bio feed\n */\ntype BioErrorType = 'not_found' | 'graphql_error' | 'invalid_request';\n\n/**\n * Log error to console with specific warning message\n *\n * @param errorType - Type of error that occurred\n * @param expertId - Expert ID that was requested\n * @param error - Optional error object for GraphQL errors\n */\nconst logError = (\n errorType: BioErrorType,\n expertId: string,\n error?: any,\n): void => {\n switch (errorType) {\n case 'not_found':\n console.warn(\n `[Expert Bio Feed] No expert found with ID \"${expertId}\". ` +\n `Please verify the expert ID is correct and the expert exists in the system.`,\n );\n break;\n case 'graphql_error':\n console.warn(\n `[Expert Bio Feed] GraphQL error occurred while fetching expert \"${expertId}\". ` +\n `Check network connection and API availability.`,\n error,\n );\n break;\n case 'invalid_request':\n console.warn(\n `[Expert Bio Feed] Invalid request for expert \"${expertId}\". ` +\n `Ensure both data-token and data-id attributes are provided.`,\n );\n break;\n }\n};\n\n// ============================================================================\n// MAIN EXPORT\n// ============================================================================\n\n/**\n * Create an expert bio feed\n *\n * Fetches and displays a single expert's bio with summary.\n * Uses element builder pattern for clean construction.\n *\n * @param props - Feed configuration\n * @returns ElementModel with bio element, styles, and events\n *\n * @example\n * ```typescript\n * const bio = expertBio({\n * token: 'my-token',\n * expertId: 'john-doe',\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With dark theme\n * const bio = expertBio({\n * token: 'my-token',\n * expertId: 'jane-smith',\n * isThemeDark: true,\n * });\n * ```\n */\nexport const expertsBio = (props: BioProps): ElementModel => {\n const { token, expertId, isThemeDark = false } = props;\n\n // Create container using ElementBuilder\n const containerBuilder = new ElementBuilder('div').withClassName(\n 'expert-bio-feed',\n );\n\n // Get element for manipulation (non-destructive)\n const container = containerBuilder.getElement();\n\n // Validate required expertId\n if (!expertId) {\n logError('invalid_request', 'unknown');\n const model = containerBuilder.build();\n return {\n element: model.element,\n styles: '',\n events: {},\n };\n }\n\n // Store validated expertId for use in async function\n const validExpertId = expertId;\n\n // Initialize state management\n const loading = new LoadingState({ isThemeDark });\n const state = new BioFeedState(loading.styles);\n\n /**\n * Fetch expert data and render\n */\n const initialize = async (): Promise<void> => {\n loading.show(container);\n\n try {\n const fetchProps = createFetchProps(token, validExpertId);\n const variables = expertsFetchStrategy.composeApiVariables(fetchProps);\n const entries = await expertsFetchStrategy.fetchEntries(variables);\n\n loading.hide();\n\n if (!entries || entries.length === 0) {\n logError('not_found', validExpertId);\n return;\n }\n\n await renderSuccess(container, entries[0], state, isThemeDark);\n } catch (error) {\n loading.hide();\n logError('graphql_error', validExpertId, error);\n }\n };\n\n // Start initialization\n initialize();\n\n // Build and return element model\n const model = containerBuilder.build();\n\n return {\n element: model.element,\n get styles() {\n return state.getStyles();\n },\n events: {\n callback: state.getShadowCallback(),\n },\n };\n};\n"],"names":["styleUtilities.setShadowStyles","model"],"mappings":"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"}
@@ -6,10 +6,8 @@ export declare const extractTopics: (entry: ExpertEntry) => string | null;
6
6
  export declare const mapExpertToBioProps: (entry: ExpertEntry, displayType: "small" | "full", isThemeDark?: boolean) => {
7
7
  name: HTMLElement | null;
8
8
  slotTwoItalic: HTMLElement | null;
9
- slotTwo: HTMLElement | null;
9
+ slotFour: HTMLElement | null;
10
10
  slotOne: HTMLElement | null;
11
- email: HTMLElement | null;
12
- linkedin: HTMLElement | null;
13
11
  phone: null;
14
12
  address: null;
15
13
  additionalContact: null;
@@ -1 +1 @@
1
- {"version":3,"file":"experts.d.ts","sourceRoot":"","sources":["../../../source/strategies/display/experts.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,eAAe,EAAsB,MAAM,0BAA0B,CAAC;AAE/E,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAoGzC,eAAO,MAAM,aAAa,GAAI,OAAO,WAAW,KAAG,MAUlD,CAAC;AA4HF,eAAO,MAAM,uBAAuB,GAAI,OAAO,WAAW,KAAG,MAAM,GAAG,IAWrE,CAAC;AA0BF,eAAO,MAAM,aAAa,GAAI,OAAO,WAAW,KAAG,MAAM,GAAG,IAW3D,CAAC;AAoVF,eAAO,MAAM,mBAAmB,GAC9B,OAAO,WAAW,EAClB,aAAa,OAAO,GAAG,MAAM,EAC7B,cAAa,OAAe;;;;;;;;;;;;;CAqC7B,CAAC;AAqBF,eAAO,MAAM,sBAAsB,EAAE,eAAe,CAAC,WAAW,CAwB/D,CAAC"}
1
+ {"version":3,"file":"experts.d.ts","sourceRoot":"","sources":["../../../source/strategies/display/experts.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,eAAe,EAAsB,MAAM,0BAA0B,CAAC;AAE/E,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAoGzC,eAAO,MAAM,aAAa,GAAI,OAAO,WAAW,KAAG,MAUlD,CAAC;AA2IF,eAAO,MAAM,uBAAuB,GAAI,OAAO,WAAW,KAAG,MAAM,GAAG,IAWrE,CAAC;AA0BF,eAAO,MAAM,aAAa,GAAI,OAAO,WAAW,KAAG,MAAM,GAAG,IAW3D,CAAC;AA2WF,eAAO,MAAM,mBAAmB,GAC9B,OAAO,WAAW,EAClB,aAAa,OAAO,GAAG,MAAM,EAC7B,cAAa,OAAe;;;;;;;;;;;CAiC7B,CAAC;AAqBF,eAAO,MAAM,sBAAsB,EAAE,eAAe,CAAC,WAAW,CAwB/D,CAAC"}
@@ -49,12 +49,21 @@ const extractPrimaryJobTitle = (entry) => {
49
49
  return entry.organizations?.[0]?.jobs?.[0]?.title || null;
50
50
  };
51
51
  const extractCampusUnit = (entry) => {
52
- const campusUnit = entry.organizations?.[0]?.jobs?.[0]?.campusUnits?.[0];
53
- if (!campusUnit) return null;
54
- return {
55
- title: campusUnit.title,
56
- url: campusUnit.link?.url
57
- };
52
+ const job = entry.organizations?.[0]?.jobs?.[0];
53
+ if (!job) return null;
54
+ const associations = [];
55
+ const collegeSchool = job.collegeSchools?.[0];
56
+ if (collegeSchool) {
57
+ associations.push({
58
+ title: collegeSchool.title,
59
+ url: collegeSchool.link?.url
60
+ });
61
+ }
62
+ const campusUnit = job.campusUnits?.[0];
63
+ if (campusUnit) {
64
+ associations.push({ title: campusUnit.title, url: campusUnit.link?.url });
65
+ }
66
+ return associations.length > 0 ? associations : null;
58
67
  };
59
68
  const extractImageData = (entry, fullName) => {
60
69
  const headshotUrl = entry.headshot?.[0]?.url;
@@ -98,15 +107,33 @@ const createJobElement = (jobTitle) => {
98
107
  if (!jobTitle) return null;
99
108
  return createTextContainer({ text: jobTitle });
100
109
  };
101
- const createAssociationElement = (association) => {
102
- if (!association) return null;
103
- if (association.url) {
104
- return createTextWithLink({
105
- text: association.title,
106
- url: association.url
107
- });
110
+ const createAssociationElement = (associations) => {
111
+ if (!associations || associations.length === 0) return null;
112
+ if (associations.length === 1) {
113
+ const association = associations[0];
114
+ if (association.url) {
115
+ return createTextWithLink({
116
+ text: association.title,
117
+ url: association.url
118
+ });
119
+ }
120
+ return createTextContainer({ text: association.title });
108
121
  }
109
- return createTextContainer({ text: association.title });
122
+ const container = document.createElement("p");
123
+ associations.forEach((association, index) => {
124
+ if (association.url) {
125
+ const link = document.createElement("a");
126
+ link.href = association.url;
127
+ link.textContent = association.title;
128
+ container.appendChild(link);
129
+ } else {
130
+ container.appendChild(document.createTextNode(association.title));
131
+ }
132
+ if (index < associations.length - 1) {
133
+ container.appendChild(document.createTextNode(", "));
134
+ }
135
+ });
136
+ return container;
110
137
  };
111
138
  const createImageElement = (imageData, linkUrl, linkLabel) => {
112
139
  if (!imageData) return null;
@@ -259,27 +286,23 @@ const mapExpertToBioProps = (entry, displayType, isThemeDark = false) => {
259
286
  const fullName = buildFullName(entry);
260
287
  const profileUrl = buildProfileUrl(entry);
261
288
  const jobTitle = extractPrimaryJobTitle(entry);
262
- const association = extractCampusUnit(entry);
289
+ const campusUnit = extractCampusUnit(entry);
263
290
  const imageData = extractImageData(entry, fullName);
264
- const contactData = extractContactData(entry);
265
291
  const description = extractDescription(entry);
266
292
  const pronouns = extractPronouns(entry);
267
293
  const topics = extractTopics(entry);
268
294
  const name = createNameElement(fullName, profileUrl, "h1");
269
295
  createJobElement(jobTitle);
270
- const associationElement = createAssociationElement(association);
296
+ const campusUnitElement = createAssociationElement(campusUnit);
271
297
  const image = createImageElement(imageData);
272
298
  const descriptionElement = createDescriptionElement(description);
273
- const contactElements = createContactElements(contactData);
274
299
  const pronounsElement = createPronounsElement(pronouns);
275
300
  const topicsElement = createExpertiseElement(topics);
276
301
  return {
277
302
  name,
278
303
  slotTwoItalic: pronounsElement,
279
- slotTwo: associationElement,
304
+ slotFour: campusUnitElement,
280
305
  slotOne: topicsElement,
281
- email: contactElements.email || null,
282
- linkedin: contactElements.linkedin || null,
283
306
  phone: null,
284
307
  address: null,
285
308
  additionalContact: null,
@@ -1 +1 @@
1
- {"version":3,"file":"experts.js","sources":["../../../source/strategies/display/experts.ts"],"sourcesContent":["/**\n * Experts Display Strategy\n *\n * Strategy for displaying expert entries using person or card elements.\n * Uses a functional composition approach with pure data extraction,\n * element creation, and card composition functions.\n *\n * @module strategies/display/experts\n */\n\nimport {\n person,\n card,\n} from '@universityofmaryland/web-elements-library/composite';\nimport {\n createTextWithLink,\n createTextContainer,\n createImageOrLinkedImage,\n} from '@universityofmaryland/web-utilities-library/elements';\nimport { DisplayStrategy, CardMappingOptions } from '../../factory/core/types';\nimport { ElementModel } from '../../_types';\nimport { ExpertEntry } from 'types/data';\n\n// ============================================================================\n// TYPE DEFINITIONS\n// ============================================================================\n\n/**\n * Contact field configuration\n */\ninterface ContactConfig {\n key: keyof Pick<\n ExpertEntry,\n 'email' | 'website' | 'linkedin' | 'twitter' | 'bluesky' | 'substack'\n >;\n label: (value: string) => string;\n url: (value: string) => string;\n}\n\n/**\n * Extracted association data\n */\ninterface AssociationData {\n title: string;\n url?: string | null;\n}\n\n/**\n * Extracted image data\n */\ninterface ImageData {\n url: string;\n altText: string;\n}\n\n/**\n * Extracted contact data\n */\ninterface ContactData {\n email?: string | null;\n website?: string | null;\n linkedin?: string | null;\n twitter?: string | null;\n bluesky?: string | null;\n substack?: string | null;\n}\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\n/**\n * Contact field configuration\n *\n * Defines how each contact type should be rendered as a link.\n */\nconst CONTACT_CONFIGS: ContactConfig[] = [\n {\n key: 'email',\n label: () => 'Email',\n url: (value) => `mailto:${value}`,\n },\n {\n key: 'website',\n label: (value) => value,\n url: (value) => value,\n },\n {\n key: 'linkedin',\n label: (value) => value,\n url: (value) => value,\n },\n {\n key: 'twitter',\n label: (value) => value,\n url: (value) => value,\n },\n {\n key: 'bluesky',\n label: (value) => value,\n url: (value) => value,\n },\n {\n key: 'substack',\n label: (value) => value,\n url: (value) => value,\n },\n];\n\n// ============================================================================\n// PURE DATA EXTRACTION FUNCTIONS\n// ============================================================================\n\n/**\n * Build full name from expert entry\n *\n * Combines prefix (optional), first, middle (optional), last name, and suffix (optional).\n *\n * @param entry - Expert entry\n * @returns Full name string\n */\nexport const buildFullName = (entry: ExpertEntry): string => {\n const parts = [\n entry.prefix,\n entry.firstName,\n entry.middleName,\n entry.lastName,\n entry.suffix,\n ].filter(Boolean);\n\n return parts.join(' ');\n};\n\n/**\n * Build expert profile URL\n *\n * @param entry - Expert entry\n * @returns Profile URL string\n */\nconst buildProfileUrl = (entry: ExpertEntry): string => {\n return `https://umdrightnow.umd.edu/expert/${entry.slug}`;\n};\n\n/**\n * Extract primary job title\n *\n * @param entry - Expert entry\n * @returns Job title string or null\n */\nconst extractPrimaryJobTitle = (entry: ExpertEntry): string | null => {\n return entry.organizations?.[0]?.jobs?.[0]?.title || null;\n};\n\n/**\n * Extract primary association (campus unit)\n *\n * @param entry - Expert entry\n * @returns Association data or null\n */\nconst extractCampusUnit = (entry: ExpertEntry): AssociationData | null => {\n const campusUnit = entry.organizations?.[0]?.jobs?.[0]?.campusUnits?.[0];\n if (!campusUnit) return null;\n\n return {\n title: campusUnit.title,\n url: campusUnit.link?.url,\n };\n};\n\n/**\n * Extract image data from headshot\n *\n * @param entry - Expert entry\n * @param fullName - Full name for alt text\n * @returns Image data or null\n */\nconst extractImageData = (\n entry: ExpertEntry,\n fullName: string,\n): ImageData | null => {\n const headshotUrl = entry.headshot?.[0]?.url;\n if (!headshotUrl) return null;\n\n return {\n url: headshotUrl,\n altText: fullName,\n };\n};\n\n/**\n * Extract contact data\n *\n * @param entry - Expert entry\n * @returns Contact data object\n */\nconst extractContactData = (entry: ExpertEntry): ContactData => {\n return {\n email: entry.email || null,\n website: entry.website || null,\n linkedin: entry.linkedin || null,\n twitter: entry.twitter || null,\n bluesky: entry.bluesky || null,\n substack: entry.substack || null,\n };\n};\n\n/**\n * Extract description based on display type\n *\n * @param entry - Expert entry\n * @param displayType - 'small' for summary, 'full' for biography\n * @returns HTML description string or null\n */\nconst extractDescription = (\n entry: ExpertEntry,\n displayType: 'small' | 'full',\n): string | null => {\n if (displayType === 'full') {\n return entry.bio?.html || null;\n }\n return entry.summary?.html || null;\n};\n\n/**\n * Extract pronouns\n *\n * @param entry - Expert entry\n * @returns Pronouns string or null\n */\nconst extractPronouns = (entry: ExpertEntry): string | null => {\n return entry.pronouns || null;\n};\n\n/**\n * Extract areas of expertise as comma-separated string\n *\n * Extracts the titles from the areasOfExpertise categories\n * and joins them with commas for display as subText.\n *\n * @param entry - Expert entry\n * @returns Comma-separated string of expertise areas or null\n *\n * @example\n * ```typescript\n * const entry = {\n * areasOfExpertise: [\n * { id: '1', title: 'Climate Change' },\n * { id: '2', title: 'Environmental Policy' },\n * { id: '3', title: 'Sustainability' }\n * ]\n * };\n * extractAreasOfExpertise(entry);\n * // Returns: \"Climate Change, Environmental Policy, Sustainability\"\n * ```\n */\nexport const extractAreasOfExpertise = (entry: ExpertEntry): string | null => {\n const areas = entry.areasOfExpertise;\n\n if (!areas || areas.length === 0) {\n return null;\n }\n\n return areas\n .slice(0, 3)\n .map((area) => area.title)\n .join(', ');\n};\n\n/**\n * Extract topics as comma-separated string\n *\n * Extracts the titles from the topics categories\n * and joins them with commas for display.\n * Limited to a maximum of 3 topics.\n *\n * @param entry - Expert entry\n * @returns Comma-separated string of topics or null\n *\n * @example\n * ```typescript\n * const entry = {\n * topics: [\n * { id: '1', title: 'Artificial Intelligence' },\n * { id: '2', title: 'Machine Learning' },\n * { id: '3', title: 'Data Science' },\n * { id: '4', title: 'Robotics' }\n * ]\n * };\n * extractTopics(entry);\n * // Returns: \"Artificial Intelligence, Machine Learning, Data Science\"\n * ```\n */\nexport const extractTopics = (entry: ExpertEntry): string | null => {\n const topics = entry.topics;\n\n if (!topics || topics.length === 0) {\n return null;\n }\n\n return topics\n .slice(0, 3)\n .map((topic) => topic.title)\n .join(', ');\n};\n\n// ============================================================================\n// ELEMENT CREATION FUNCTIONS\n// ============================================================================\n\n/**\n * Create name element with link\n *\n * @param fullName - Full name text\n * @param url - Profile URL\n * @param containerTag - HTML tag for container (default: undefined)\n * @returns Name element or null\n */\nconst createNameElement = (\n fullName: string,\n url: string,\n containerTag?: 'h1' | 'h2' | 'h3',\n): HTMLElement | null => {\n return createTextWithLink({\n text: fullName,\n url,\n containerTag,\n });\n};\n\n/**\n * Create job title element\n *\n * @param jobTitle - Job title text\n * @returns Job element or null\n */\nconst createJobElement = (jobTitle: string | null): HTMLElement | null => {\n if (!jobTitle) return null;\n return createTextContainer({ text: jobTitle });\n};\n\n/**\n * Create association element (with optional link)\n *\n * @param association - Association data\n * @returns Association element or null\n */\nconst createAssociationElement = (\n association: AssociationData | null,\n): HTMLElement | null => {\n if (!association) return null;\n\n if (association.url) {\n return createTextWithLink({\n text: association.title,\n url: association.url,\n });\n }\n\n return createTextContainer({ text: association.title });\n};\n\n/**\n * Create image element (with optional link)\n *\n * @param imageData - Image data\n * @param linkUrl - Optional link URL\n * @param linkLabel - Optional link label\n * @returns Image element or null\n */\nconst createImageElement = (\n imageData: ImageData | null,\n linkUrl?: string,\n linkLabel?: string,\n): HTMLImageElement | HTMLAnchorElement | null => {\n if (!imageData) return null;\n\n return createImageOrLinkedImage({\n imageUrl: imageData.url,\n altText: imageData.altText,\n linkUrl,\n linkLabel,\n });\n};\n\n/**\n * Create description element\n *\n * @param description - HTML description text\n * @returns Description element or null\n */\nconst createDescriptionElement = (\n description: string | null,\n): HTMLElement | null => {\n if (!description) return null;\n return createTextContainer({ text: description, allowHTML: true });\n};\n\n/**\n * Create pronouns element\n *\n * @param pronouns - Pronouns text\n * @returns Pronouns element or null\n */\nconst createPronounsElement = (pronouns: string | null): HTMLElement | null => {\n if (!pronouns) return null;\n return createTextContainer({ text: pronouns });\n};\n\n/**\n * Create pronouns element\n *\n * @param pronouns - Pronouns text\n * @returns Pronouns element or null\n */\nconst createExpertiseElement = (\n expertise: string | null,\n): HTMLElement | null => {\n if (!expertise) return null;\n return createTextContainer({ text: expertise });\n};\n\n/**\n * Create contact elements from contact data\n *\n * @param contactData - Contact data object\n * @returns Object with contact elements keyed by contact type\n */\nconst createContactElements = (\n contactData: ContactData,\n): { [key: string]: HTMLElement | null } => {\n return CONTACT_CONFIGS.reduce<{ [key: string]: HTMLElement | null }>(\n (elements, config) => {\n const value = contactData[config.key];\n if (!value) return elements;\n\n const element = createTextWithLink({\n text: config.label(value),\n url: config.url(value),\n });\n\n elements[config.key] = element;\n return elements;\n },\n {},\n );\n};\n\n// ============================================================================\n// CARD COMPOSITION FUNCTIONS\n// ============================================================================\n\n/**\n * Create props for person block card\n *\n * @param entry - Expert entry\n * @param options - Card mapping options\n * @returns Person block card element\n */\nconst createBlockCardProps = (\n entry: ExpertEntry,\n options: CardMappingOptions,\n): ElementModel => {\n const { isThemeDark = false } = options;\n\n // Extract data\n const fullName = buildFullName(entry);\n const profileUrl = buildProfileUrl(entry);\n const jobTitle = extractPrimaryJobTitle(entry);\n const campusUnit = extractCampusUnit(entry);\n const imageData = extractImageData(entry, fullName);\n const pronouns = extractPronouns(entry);\n const topics = extractTopics(entry);\n\n // Create elements\n const name = createNameElement(fullName, profileUrl);\n const job = createJobElement(jobTitle);\n const campusUnitElement = createAssociationElement(campusUnit);\n const image = createImageElement(\n imageData,\n profileUrl,\n `View profile for ${fullName}`,\n );\n const pronounsElement = createPronounsElement(pronouns);\n const topicsElement = createExpertiseElement(topics);\n\n return person.block({\n name,\n slotOne: topicsElement,\n slotTwo: campusUnitElement,\n slotThreeItalic: pronounsElement,\n image,\n isThemeDark,\n });\n};\n\n/**\n * Create props for person list card\n *\n * @param entry - Expert entry\n * @param options - Card mapping options\n * @returns Person list card element\n */\nconst createListCardProps = (\n entry: ExpertEntry,\n options: CardMappingOptions,\n): ElementModel => {\n const { isThemeDark = false } = options;\n\n // Extract data\n const fullName = buildFullName(entry);\n const profileUrl = buildProfileUrl(entry);\n const jobTitle = extractPrimaryJobTitle(entry);\n const campusUnit = extractCampusUnit(entry);\n const imageData = extractImageData(entry, fullName);\n const pronouns = extractPronouns(entry);\n const topics = extractTopics(entry);\n\n // Create elements\n const name = createNameElement(fullName, profileUrl);\n const job = createJobElement(jobTitle);\n const campusUnitElement = createAssociationElement(campusUnit);\n const image = createImageElement(\n imageData,\n profileUrl,\n `View profile for ${fullName}`,\n );\n const pronounsElement = createPronounsElement(pronouns);\n const topicsElement = createExpertiseElement(topics);\n\n return person.list({\n name,\n slotOne: topicsElement,\n slotThreeItalic: pronounsElement,\n slotTwo: campusUnitElement,\n image,\n isThemeDark,\n });\n};\n\n/**\n * Create props for person tabular card\n *\n * @param entry - Expert entry\n * @param options - Card mapping options\n * @returns Person tabular card element\n */\nconst createTabularCardProps = (\n entry: ExpertEntry,\n options: CardMappingOptions,\n): ElementModel => {\n const { isThemeDark = false } = options;\n\n // Extract data\n const fullName = buildFullName(entry);\n const profileUrl = buildProfileUrl(entry);\n const jobTitle = extractPrimaryJobTitle(entry);\n const campusUnit = extractCampusUnit(entry);\n const imageData = extractImageData(entry, fullName);\n const contactData = extractContactData(entry);\n const pronouns = extractPronouns(entry);\n const topics = extractTopics(entry);\n\n // Create elements\n const name = createNameElement(fullName, profileUrl);\n const job = createJobElement(jobTitle);\n const campusUnitElement = createAssociationElement(campusUnit);\n const image = createImageElement(\n imageData,\n profileUrl,\n `View profile for ${fullName}`,\n );\n const contactElements = createContactElements(contactData);\n const pronounsElement = createPronounsElement(pronouns);\n const topicsElement = createExpertiseElement(topics);\n\n return person.tabular({\n name,\n slotOne: topicsElement,\n slotThreeItalic: pronounsElement,\n slotTwo: campusUnitElement,\n image,\n ...contactElements,\n isThemeDark,\n });\n};\n\n/**\n * Create props for overlay card\n *\n * @param entry - Expert entry\n * @param options - Card mapping options\n * @returns Overlay card element\n */\nconst createOverlayCardProps = (\n entry: ExpertEntry,\n options: CardMappingOptions,\n): ElementModel => {\n const { isThemeDark = false } = options;\n\n // Extract data\n const fullName = buildFullName(entry);\n const profileUrl = buildProfileUrl(entry);\n const jobTitle = extractPrimaryJobTitle(entry);\n const campusUnit = extractCampusUnit(entry);\n const topics = extractTopics(entry);\n const imageData = extractImageData(entry, fullName);\n\n // Create elements\n const headline = createNameElement(fullName, profileUrl);\n const topicsElement = createExpertiseElement(topics);\n const campusUnitElement = createAssociationElement(campusUnit);\n const backgroundImage = createImageElement(\n imageData,\n profileUrl,\n `View profile for ${fullName}`,\n );\n\n return card.overlay.image({\n eyebrow: campusUnitElement,\n headline,\n text: topicsElement,\n backgroundImage,\n isThemeDark,\n });\n};\n\n/**\n * Map expert entry to PersonBio props\n *\n * Shared helper for creating PersonBio props from expert data.\n * Used by both the display strategy and bio feed for consistency.\n *\n * @param entry - Expert entry from API\n * @param displayType - 'small' for summary, 'full' for biography\n * @param isThemeDark - Dark theme flag\n * @returns Props for person.bio.full() or person.bio.small()\n *\n * @example\n * ```typescript\n * const bioProps = mapExpertToBioProps(expert, 'full', false);\n * const bioElement = person.bio.full(bioProps);\n * ```\n */\nexport const mapExpertToBioProps = (\n entry: ExpertEntry,\n displayType: 'small' | 'full',\n isThemeDark: boolean = false,\n) => {\n // Extract data\n const fullName = buildFullName(entry);\n const profileUrl = buildProfileUrl(entry);\n const jobTitle = extractPrimaryJobTitle(entry);\n const association = extractCampusUnit(entry);\n const imageData = extractImageData(entry, fullName);\n const contactData = extractContactData(entry);\n const description = extractDescription(entry, displayType);\n const pronouns = extractPronouns(entry);\n const topics = extractTopics(entry);\n\n // Create elements\n const name = createNameElement(fullName, profileUrl, 'h1');\n const job = createJobElement(jobTitle);\n const associationElement = createAssociationElement(association);\n const image = createImageElement(imageData);\n const descriptionElement = createDescriptionElement(description);\n const contactElements = createContactElements(contactData);\n const pronounsElement = createPronounsElement(pronouns);\n const topicsElement = createExpertiseElement(topics);\n\n return {\n name,\n slotTwoItalic: pronounsElement,\n slotTwo: associationElement,\n slotOne: topicsElement,\n email: contactElements.email || null,\n linkedin: contactElements.linkedin || null,\n phone: null,\n address: null,\n additionalContact: null,\n image,\n description: descriptionElement,\n isThemeDark,\n };\n};\n\n// ============================================================================\n// DISPLAY STRATEGY\n// ============================================================================\n\n/**\n * Experts display strategy\n *\n * Maps expert entries to person elements for profile display.\n * Optimized for displaying faculty and expert profiles with contact\n * information and skills.\n *\n * @example\n * ```typescript\n * const feed = createBaseFeed({\n * displayStrategy: expertsDisplayStrategy,\n * // ...\n * });\n * ```\n */\nexport const expertsDisplayStrategy: DisplayStrategy<ExpertEntry> = {\n layoutType: 'list',\n\n mapEntryToCard: (\n entry: ExpertEntry,\n options: CardMappingOptions,\n ): ElementModel => {\n const { isOverlay = false, cardType = 'block' } = options;\n\n // Handle overlay card type (requires headshot)\n if (isOverlay && entry.headshot?.[0]?.url) {\n return createOverlayCardProps(entry, options);\n }\n\n // Route to appropriate card type\n switch (cardType) {\n case 'list':\n return createListCardProps(entry, options);\n case 'tabular':\n return createTabularCardProps(entry, options);\n default:\n return createBlockCardProps(entry, options);\n }\n },\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"experts.js","sources":["../../../source/strategies/display/experts.ts"],"sourcesContent":["/**\n * Experts Display Strategy\n *\n * Strategy for displaying expert entries using person or card elements.\n * Uses a functional composition approach with pure data extraction,\n * element creation, and card composition functions.\n *\n * @module strategies/display/experts\n */\n\nimport {\n person,\n card,\n} from '@universityofmaryland/web-elements-library/composite';\nimport {\n createTextWithLink,\n createTextContainer,\n createImageOrLinkedImage,\n} from '@universityofmaryland/web-utilities-library/elements';\nimport { DisplayStrategy, CardMappingOptions } from '../../factory/core/types';\nimport { ElementModel } from '../../_types';\nimport { ExpertEntry } from 'types/data';\n\n// ============================================================================\n// TYPE DEFINITIONS\n// ============================================================================\n\n/**\n * Contact field configuration\n */\ninterface ContactConfig {\n key: keyof Pick<\n ExpertEntry,\n 'email' | 'website' | 'linkedin' | 'twitter' | 'bluesky' | 'substack'\n >;\n label: (value: string) => string;\n url: (value: string) => string;\n}\n\n/**\n * Extracted association data\n */\ninterface AssociationData {\n title: string;\n url?: string | null;\n}\n\n/**\n * Extracted image data\n */\ninterface ImageData {\n url: string;\n altText: string;\n}\n\n/**\n * Extracted contact data\n */\ninterface ContactData {\n email?: string | null;\n website?: string | null;\n linkedin?: string | null;\n twitter?: string | null;\n bluesky?: string | null;\n substack?: string | null;\n}\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\n/**\n * Contact field configuration\n *\n * Defines how each contact type should be rendered as a link.\n */\nconst CONTACT_CONFIGS: ContactConfig[] = [\n {\n key: 'email',\n label: () => 'Email',\n url: (value) => `mailto:${value}`,\n },\n {\n key: 'website',\n label: (value) => value,\n url: (value) => value,\n },\n {\n key: 'linkedin',\n label: (value) => value,\n url: (value) => value,\n },\n {\n key: 'twitter',\n label: (value) => value,\n url: (value) => value,\n },\n {\n key: 'bluesky',\n label: (value) => value,\n url: (value) => value,\n },\n {\n key: 'substack',\n label: (value) => value,\n url: (value) => value,\n },\n];\n\n// ============================================================================\n// PURE DATA EXTRACTION FUNCTIONS\n// ============================================================================\n\n/**\n * Build full name from expert entry\n *\n * Combines prefix (optional), first, middle (optional), last name, and suffix (optional).\n *\n * @param entry - Expert entry\n * @returns Full name string\n */\nexport const buildFullName = (entry: ExpertEntry): string => {\n const parts = [\n entry.prefix,\n entry.firstName,\n entry.middleName,\n entry.lastName,\n entry.suffix,\n ].filter(Boolean);\n\n return parts.join(' ');\n};\n\n/**\n * Build expert profile URL\n *\n * @param entry - Expert entry\n * @returns Profile URL string\n */\nconst buildProfileUrl = (entry: ExpertEntry): string => {\n return `https://umdrightnow.umd.edu/expert/${entry.slug}`;\n};\n\n/**\n * Extract primary job title\n *\n * @param entry - Expert entry\n * @returns Job title string or null\n */\nconst extractPrimaryJobTitle = (entry: ExpertEntry): string | null => {\n return entry.organizations?.[0]?.jobs?.[0]?.title || null;\n};\n\n/**\n * Extract primary association (college/school and campus unit)\n *\n * Combines collegeSchools and campusUnits into a comma-separated string,\n * with college/school listed first.\n *\n * @param entry - Expert entry\n * @returns Association data or null\n */\nconst extractCampusUnit = (entry: ExpertEntry): AssociationData[] | null => {\n const job = entry.organizations?.[0]?.jobs?.[0];\n if (!job) return null;\n\n const associations: AssociationData[] = [];\n\n const collegeSchool = job.collegeSchools?.[0];\n if (collegeSchool) {\n associations.push({\n title: collegeSchool.title,\n url: collegeSchool.link?.url,\n });\n }\n\n const campusUnit = job.campusUnits?.[0];\n if (campusUnit) {\n associations.push({ title: campusUnit.title, url: campusUnit.link?.url });\n }\n\n return associations.length > 0 ? associations : null;\n};\n\n/**\n * Extract image data from headshot\n *\n * @param entry - Expert entry\n * @param fullName - Full name for alt text\n * @returns Image data or null\n */\nconst extractImageData = (\n entry: ExpertEntry,\n fullName: string,\n): ImageData | null => {\n const headshotUrl = entry.headshot?.[0]?.url;\n if (!headshotUrl) return null;\n\n return {\n url: headshotUrl,\n altText: fullName,\n };\n};\n\n/**\n * Extract contact data\n *\n * @param entry - Expert entry\n * @returns Contact data object\n */\nconst extractContactData = (entry: ExpertEntry): ContactData => {\n return {\n email: entry.email || null,\n website: entry.website || null,\n linkedin: entry.linkedin || null,\n twitter: entry.twitter || null,\n bluesky: entry.bluesky || null,\n substack: entry.substack || null,\n };\n};\n\n/**\n * Extract description based on display type\n *\n * @param entry - Expert entry\n * @param displayType - 'small' for summary, 'full' for biography\n * @returns HTML description string or null\n */\nconst extractDescription = (\n entry: ExpertEntry,\n displayType: 'small' | 'full',\n): string | null => {\n if (displayType === 'full') {\n return entry.bio?.html || null;\n }\n return entry.summary?.html || null;\n};\n\n/**\n * Extract pronouns\n *\n * @param entry - Expert entry\n * @returns Pronouns string or null\n */\nconst extractPronouns = (entry: ExpertEntry): string | null => {\n return entry.pronouns || null;\n};\n\n/**\n * Extract areas of expertise as comma-separated string\n *\n * Extracts the titles from the areasOfExpertise categories\n * and joins them with commas for display as subText.\n *\n * @param entry - Expert entry\n * @returns Comma-separated string of expertise areas or null\n *\n * @example\n * ```typescript\n * const entry = {\n * areasOfExpertise: [\n * { id: '1', title: 'Climate Change' },\n * { id: '2', title: 'Environmental Policy' },\n * { id: '3', title: 'Sustainability' }\n * ]\n * };\n * extractAreasOfExpertise(entry);\n * // Returns: \"Climate Change, Environmental Policy, Sustainability\"\n * ```\n */\nexport const extractAreasOfExpertise = (entry: ExpertEntry): string | null => {\n const areas = entry.areasOfExpertise;\n\n if (!areas || areas.length === 0) {\n return null;\n }\n\n return areas\n .slice(0, 3)\n .map((area) => area.title)\n .join(', ');\n};\n\n/**\n * Extract topics as comma-separated string\n *\n * Extracts the titles from the topics categories\n * and joins them with commas for display.\n * Limited to a maximum of 3 topics.\n *\n * @param entry - Expert entry\n * @returns Comma-separated string of topics or null\n *\n * @example\n * ```typescript\n * const entry = {\n * topics: [\n * { id: '1', title: 'Artificial Intelligence' },\n * { id: '2', title: 'Machine Learning' },\n * { id: '3', title: 'Data Science' },\n * { id: '4', title: 'Robotics' }\n * ]\n * };\n * extractTopics(entry);\n * // Returns: \"Artificial Intelligence, Machine Learning, Data Science\"\n * ```\n */\nexport const extractTopics = (entry: ExpertEntry): string | null => {\n const topics = entry.topics;\n\n if (!topics || topics.length === 0) {\n return null;\n }\n\n return topics\n .slice(0, 3)\n .map((topic) => topic.title)\n .join(', ');\n};\n\n// ============================================================================\n// ELEMENT CREATION FUNCTIONS\n// ============================================================================\n\n/**\n * Create name element with link\n *\n * @param fullName - Full name text\n * @param url - Profile URL\n * @param containerTag - HTML tag for container (default: undefined)\n * @returns Name element or null\n */\nconst createNameElement = (\n fullName: string,\n url: string,\n containerTag?: 'h1' | 'h2' | 'h3',\n): HTMLElement | null => {\n return createTextWithLink({\n text: fullName,\n url,\n containerTag,\n });\n};\n\n/**\n * Create job title element\n *\n * @param jobTitle - Job title text\n * @returns Job element or null\n */\nconst createJobElement = (jobTitle: string | null): HTMLElement | null => {\n if (!jobTitle) return null;\n return createTextContainer({ text: jobTitle });\n};\n\n/**\n * Create association element (with optional links)\n *\n * Builds a container with individually-linked items separated by commas.\n *\n * @param associations - Array of association data\n * @returns Association element or null\n */\nconst createAssociationElement = (\n associations: AssociationData[] | null,\n): HTMLElement | null => {\n if (!associations || associations.length === 0) return null;\n\n if (associations.length === 1) {\n const association = associations[0];\n if (association.url) {\n return createTextWithLink({\n text: association.title,\n url: association.url,\n });\n }\n return createTextContainer({ text: association.title });\n }\n\n const container = document.createElement('p');\n\n associations.forEach((association, index) => {\n if (association.url) {\n const link = document.createElement('a');\n link.href = association.url;\n link.textContent = association.title;\n container.appendChild(link);\n } else {\n container.appendChild(document.createTextNode(association.title));\n }\n\n if (index < associations.length - 1) {\n container.appendChild(document.createTextNode(', '));\n }\n });\n\n return container;\n};\n\n/**\n * Create image element (with optional link)\n *\n * @param imageData - Image data\n * @param linkUrl - Optional link URL\n * @param linkLabel - Optional link label\n * @returns Image element or null\n */\nconst createImageElement = (\n imageData: ImageData | null,\n linkUrl?: string,\n linkLabel?: string,\n): HTMLImageElement | HTMLAnchorElement | null => {\n if (!imageData) return null;\n\n return createImageOrLinkedImage({\n imageUrl: imageData.url,\n altText: imageData.altText,\n linkUrl,\n linkLabel,\n });\n};\n\n/**\n * Create description element\n *\n * @param description - HTML description text\n * @returns Description element or null\n */\nconst createDescriptionElement = (\n description: string | null,\n): HTMLElement | null => {\n if (!description) return null;\n return createTextContainer({ text: description, allowHTML: true });\n};\n\n/**\n * Create pronouns element\n *\n * @param pronouns - Pronouns text\n * @returns Pronouns element or null\n */\nconst createPronounsElement = (pronouns: string | null): HTMLElement | null => {\n if (!pronouns) return null;\n return createTextContainer({ text: pronouns });\n};\n\n/**\n * Create pronouns element\n *\n * @param pronouns - Pronouns text\n * @returns Pronouns element or null\n */\nconst createExpertiseElement = (\n expertise: string | null,\n): HTMLElement | null => {\n if (!expertise) return null;\n return createTextContainer({ text: expertise });\n};\n\n/**\n * Create contact elements from contact data\n *\n * @param contactData - Contact data object\n * @returns Object with contact elements keyed by contact type\n */\nconst createContactElements = (\n contactData: ContactData,\n): { [key: string]: HTMLElement | null } => {\n return CONTACT_CONFIGS.reduce<{ [key: string]: HTMLElement | null }>(\n (elements, config) => {\n const value = contactData[config.key];\n if (!value) return elements;\n\n const element = createTextWithLink({\n text: config.label(value),\n url: config.url(value),\n });\n\n elements[config.key] = element;\n return elements;\n },\n {},\n );\n};\n\n// ============================================================================\n// CARD COMPOSITION FUNCTIONS\n// ============================================================================\n\n/**\n * Create props for person block card\n *\n * @param entry - Expert entry\n * @param options - Card mapping options\n * @returns Person block card element\n */\nconst createBlockCardProps = (\n entry: ExpertEntry,\n options: CardMappingOptions,\n): ElementModel => {\n const { isThemeDark = false } = options;\n\n // Extract data\n const fullName = buildFullName(entry);\n const profileUrl = buildProfileUrl(entry);\n const jobTitle = extractPrimaryJobTitle(entry);\n const campusUnit = extractCampusUnit(entry);\n const imageData = extractImageData(entry, fullName);\n const pronouns = extractPronouns(entry);\n const topics = extractTopics(entry);\n\n // Create elements\n const name = createNameElement(fullName, profileUrl);\n const job = createJobElement(jobTitle);\n const campusUnitElement = createAssociationElement(campusUnit);\n const image = createImageElement(\n imageData,\n profileUrl,\n `View profile for ${fullName}`,\n );\n const pronounsElement = createPronounsElement(pronouns);\n const topicsElement = createExpertiseElement(topics);\n\n return person.block({\n name,\n slotOne: topicsElement,\n slotTwo: campusUnitElement,\n slotThreeItalic: pronounsElement,\n image,\n isThemeDark,\n });\n};\n\n/**\n * Create props for person list card\n *\n * @param entry - Expert entry\n * @param options - Card mapping options\n * @returns Person list card element\n */\nconst createListCardProps = (\n entry: ExpertEntry,\n options: CardMappingOptions,\n): ElementModel => {\n const { isThemeDark = false } = options;\n\n // Extract data\n const fullName = buildFullName(entry);\n const profileUrl = buildProfileUrl(entry);\n const jobTitle = extractPrimaryJobTitle(entry);\n const campusUnit = extractCampusUnit(entry);\n const imageData = extractImageData(entry, fullName);\n const pronouns = extractPronouns(entry);\n const topics = extractTopics(entry);\n\n // Create elements\n const name = createNameElement(fullName, profileUrl);\n const job = createJobElement(jobTitle);\n const campusUnitElement = createAssociationElement(campusUnit);\n const image = createImageElement(\n imageData,\n profileUrl,\n `View profile for ${fullName}`,\n );\n const pronounsElement = createPronounsElement(pronouns);\n const topicsElement = createExpertiseElement(topics);\n\n return person.list({\n name,\n slotOne: topicsElement,\n slotThreeItalic: pronounsElement,\n slotTwo: campusUnitElement,\n image,\n isThemeDark,\n });\n};\n\n/**\n * Create props for person tabular card\n *\n * @param entry - Expert entry\n * @param options - Card mapping options\n * @returns Person tabular card element\n */\nconst createTabularCardProps = (\n entry: ExpertEntry,\n options: CardMappingOptions,\n): ElementModel => {\n const { isThemeDark = false } = options;\n\n // Extract data\n const fullName = buildFullName(entry);\n const profileUrl = buildProfileUrl(entry);\n const jobTitle = extractPrimaryJobTitle(entry);\n const campusUnit = extractCampusUnit(entry);\n const imageData = extractImageData(entry, fullName);\n const contactData = extractContactData(entry);\n const pronouns = extractPronouns(entry);\n const topics = extractTopics(entry);\n\n // Create elements\n const name = createNameElement(fullName, profileUrl);\n const job = createJobElement(jobTitle);\n const campusUnitElement = createAssociationElement(campusUnit);\n const image = createImageElement(\n imageData,\n profileUrl,\n `View profile for ${fullName}`,\n );\n const contactElements = createContactElements(contactData);\n const pronounsElement = createPronounsElement(pronouns);\n const topicsElement = createExpertiseElement(topics);\n\n return person.tabular({\n name,\n slotOne: topicsElement,\n slotThreeItalic: pronounsElement,\n slotTwo: campusUnitElement,\n image,\n ...contactElements,\n isThemeDark,\n });\n};\n\n/**\n * Create props for overlay card\n *\n * @param entry - Expert entry\n * @param options - Card mapping options\n * @returns Overlay card element\n */\nconst createOverlayCardProps = (\n entry: ExpertEntry,\n options: CardMappingOptions,\n): ElementModel => {\n const { isThemeDark = false } = options;\n\n // Extract data\n const fullName = buildFullName(entry);\n const profileUrl = buildProfileUrl(entry);\n const jobTitle = extractPrimaryJobTitle(entry);\n const campusUnit = extractCampusUnit(entry);\n const topics = extractTopics(entry);\n const imageData = extractImageData(entry, fullName);\n\n // Create elements\n const headline = createNameElement(fullName, profileUrl);\n const topicsElement = createExpertiseElement(topics);\n const campusUnitElement = createAssociationElement(campusUnit);\n const backgroundImage = createImageElement(\n imageData,\n profileUrl,\n `View profile for ${fullName}`,\n );\n\n return card.overlay.image({\n eyebrow: campusUnitElement,\n headline,\n text: topicsElement,\n backgroundImage,\n isThemeDark,\n });\n};\n\n/**\n * Map expert entry to PersonBio props\n *\n * Shared helper for creating PersonBio props from expert data.\n * Used by both the display strategy and bio feed for consistency.\n *\n * @param entry - Expert entry from API\n * @param displayType - 'small' for summary, 'full' for biography\n * @param isThemeDark - Dark theme flag\n * @returns Props for person.bio.full() or person.bio.small()\n *\n * @example\n * ```typescript\n * const bioProps = mapExpertToBioProps(expert, 'full', false);\n * const bioElement = person.bio.full(bioProps);\n * ```\n */\nexport const mapExpertToBioProps = (\n entry: ExpertEntry,\n displayType: 'small' | 'full',\n isThemeDark: boolean = false,\n) => {\n // Extract data\n const fullName = buildFullName(entry);\n const profileUrl = buildProfileUrl(entry);\n const jobTitle = extractPrimaryJobTitle(entry);\n const campusUnit = extractCampusUnit(entry);\n const imageData = extractImageData(entry, fullName);\n const description = extractDescription(entry, displayType);\n const pronouns = extractPronouns(entry);\n const topics = extractTopics(entry);\n\n // Create elements\n const name = createNameElement(fullName, profileUrl, 'h1');\n const job = createJobElement(jobTitle);\n const campusUnitElement = createAssociationElement(campusUnit);\n const image = createImageElement(imageData);\n const descriptionElement = createDescriptionElement(description);\n const pronounsElement = createPronounsElement(pronouns);\n const topicsElement = createExpertiseElement(topics);\n\n return {\n name,\n slotTwoItalic: pronounsElement,\n slotFour: campusUnitElement,\n slotOne: topicsElement,\n phone: null,\n address: null,\n additionalContact: null,\n image,\n description: descriptionElement,\n isThemeDark,\n };\n};\n\n// ============================================================================\n// DISPLAY STRATEGY\n// ============================================================================\n\n/**\n * Experts display strategy\n *\n * Maps expert entries to person elements for profile display.\n * Optimized for displaying faculty and expert profiles with contact\n * information and skills.\n *\n * @example\n * ```typescript\n * const feed = createBaseFeed({\n * displayStrategy: expertsDisplayStrategy,\n * // ...\n * });\n * ```\n */\nexport const expertsDisplayStrategy: DisplayStrategy<ExpertEntry> = {\n layoutType: 'list',\n\n mapEntryToCard: (\n entry: ExpertEntry,\n options: CardMappingOptions,\n ): ElementModel => {\n const { isOverlay = false, cardType = 'block' } = options;\n\n // Handle overlay card type (requires headshot)\n if (isOverlay && entry.headshot?.[0]?.url) {\n return createOverlayCardProps(entry, options);\n }\n\n // Route to appropriate card type\n switch (cardType) {\n case 'list':\n return createListCardProps(entry, options);\n case 'tabular':\n return createTabularCardProps(entry, options);\n default:\n return createBlockCardProps(entry, options);\n }\n },\n};\n"],"names":[],"mappings":";;AA4EA,MAAM,kBAAmC;AAAA,EACvC;AAAA,IACE,KAAK;AAAA,IACL,OAAO,MAAM;AAAA,IACb,KAAK,CAAC,UAAU,UAAU,KAAK;AAAA,EAAA;AAAA,EAEjC;AAAA,IACE,KAAK;AAAA,IACL,OAAO,CAAC,UAAU;AAAA,IAClB,KAAK,CAAC,UAAU;AAAA,EAAA;AAAA,EAElB;AAAA,IACE,KAAK;AAAA,IACL,OAAO,CAAC,UAAU;AAAA,IAClB,KAAK,CAAC,UAAU;AAAA,EAAA;AAAA,EAElB;AAAA,IACE,KAAK;AAAA,IACL,OAAO,CAAC,UAAU;AAAA,IAClB,KAAK,CAAC,UAAU;AAAA,EAAA;AAAA,EAElB;AAAA,IACE,KAAK;AAAA,IACL,OAAO,CAAC,UAAU;AAAA,IAClB,KAAK,CAAC,UAAU;AAAA,EAAA;AAAA,EAElB;AAAA,IACE,KAAK;AAAA,IACL,OAAO,CAAC,UAAU;AAAA,IAClB,KAAK,CAAC,UAAU;AAAA,EAAA;AAEpB;AAcO,MAAM,gBAAgB,CAAC,UAA+B;AAC3D,QAAM,QAAQ;AAAA,IACZ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,EAAA,EACN,OAAO,OAAO;AAEhB,SAAO,MAAM,KAAK,GAAG;AACvB;AAQA,MAAM,kBAAkB,CAAC,UAA+B;AACtD,SAAO,sCAAsC,MAAM,IAAI;AACzD;AAQA,MAAM,yBAAyB,CAAC,UAAsC;AACpE,SAAO,MAAM,gBAAgB,CAAC,GAAG,OAAO,CAAC,GAAG,SAAS;AACvD;AAWA,MAAM,oBAAoB,CAAC,UAAiD;AAC1E,QAAM,MAAM,MAAM,gBAAgB,CAAC,GAAG,OAAO,CAAC;AAC9C,MAAI,CAAC,IAAK,QAAO;AAEjB,QAAM,eAAkC,CAAA;AAExC,QAAM,gBAAgB,IAAI,iBAAiB,CAAC;AAC5C,MAAI,eAAe;AACjB,iBAAa,KAAK;AAAA,MAChB,OAAO,cAAc;AAAA,MACrB,KAAK,cAAc,MAAM;AAAA,IAAA,CAC1B;AAAA,EACH;AAEA,QAAM,aAAa,IAAI,cAAc,CAAC;AACtC,MAAI,YAAY;AACd,iBAAa,KAAK,EAAE,OAAO,WAAW,OAAO,KAAK,WAAW,MAAM,KAAK;AAAA,EAC1E;AAEA,SAAO,aAAa,SAAS,IAAI,eAAe;AAClD;AASA,MAAM,mBAAmB,CACvB,OACA,aACqB;AACrB,QAAM,cAAc,MAAM,WAAW,CAAC,GAAG;AACzC,MAAI,CAAC,YAAa,QAAO;AAEzB,SAAO;AAAA,IACL,KAAK;AAAA,IACL,SAAS;AAAA,EAAA;AAEb;AAQA,MAAM,qBAAqB,CAAC,UAAoC;AAC9D,SAAO;AAAA,IACL,OAAO,MAAM,SAAS;AAAA,IACtB,SAAS,MAAM,WAAW;AAAA,IAC1B,UAAU,MAAM,YAAY;AAAA,IAC5B,SAAS,MAAM,WAAW;AAAA,IAC1B,SAAS,MAAM,WAAW;AAAA,IAC1B,UAAU,MAAM,YAAY;AAAA,EAAA;AAEhC;AASA,MAAM,qBAAqB,CACzB,OACA,gBACkB;AAIlB,SAAO,MAAM,SAAS,QAAQ;AAChC;AAQA,MAAM,kBAAkB,CAAC,UAAsC;AAC7D,SAAO,MAAM,YAAY;AAC3B;AA6DO,MAAM,gBAAgB,CAAC,UAAsC;AAClE,QAAM,SAAS,MAAM;AAErB,MAAI,CAAC,UAAU,OAAO,WAAW,GAAG;AAClC,WAAO;AAAA,EACT;AAEA,SAAO,OACJ,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,UAAU,MAAM,KAAK,EAC1B,KAAK,IAAI;AACd;AAcA,MAAM,oBAAoB,CACxB,UACA,KACA,iBACuB;AACvB,SAAO,mBAAmB;AAAA,IACxB,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EAAA,CACD;AACH;AAQA,MAAM,mBAAmB,CAAC,aAAgD;AACxE,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,oBAAoB,EAAE,MAAM,UAAU;AAC/C;AAUA,MAAM,2BAA2B,CAC/B,iBACuB;AACvB,MAAI,CAAC,gBAAgB,aAAa,WAAW,EAAG,QAAO;AAEvD,MAAI,aAAa,WAAW,GAAG;AAC7B,UAAM,cAAc,aAAa,CAAC;AAClC,QAAI,YAAY,KAAK;AACnB,aAAO,mBAAmB;AAAA,QACxB,MAAM,YAAY;AAAA,QAClB,KAAK,YAAY;AAAA,MAAA,CAClB;AAAA,IACH;AACA,WAAO,oBAAoB,EAAE,MAAM,YAAY,OAAO;AAAA,EACxD;AAEA,QAAM,YAAY,SAAS,cAAc,GAAG;AAE5C,eAAa,QAAQ,CAAC,aAAa,UAAU;AAC3C,QAAI,YAAY,KAAK;AACnB,YAAM,OAAO,SAAS,cAAc,GAAG;AACvC,WAAK,OAAO,YAAY;AACxB,WAAK,cAAc,YAAY;AAC/B,gBAAU,YAAY,IAAI;AAAA,IAC5B,OAAO;AACL,gBAAU,YAAY,SAAS,eAAe,YAAY,KAAK,CAAC;AAAA,IAClE;AAEA,QAAI,QAAQ,aAAa,SAAS,GAAG;AACnC,gBAAU,YAAY,SAAS,eAAe,IAAI,CAAC;AAAA,IACrD;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAUA,MAAM,qBAAqB,CACzB,WACA,SACA,cACgD;AAChD,MAAI,CAAC,UAAW,QAAO;AAEvB,SAAO,yBAAyB;AAAA,IAC9B,UAAU,UAAU;AAAA,IACpB,SAAS,UAAU;AAAA,IACnB;AAAA,IACA;AAAA,EAAA,CACD;AACH;AAQA,MAAM,2BAA2B,CAC/B,gBACuB;AACvB,MAAI,CAAC,YAAa,QAAO;AACzB,SAAO,oBAAoB,EAAE,MAAM,aAAa,WAAW,MAAM;AACnE;AAQA,MAAM,wBAAwB,CAAC,aAAgD;AAC7E,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,oBAAoB,EAAE,MAAM,UAAU;AAC/C;AAQA,MAAM,yBAAyB,CAC7B,cACuB;AACvB,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO,oBAAoB,EAAE,MAAM,WAAW;AAChD;AAQA,MAAM,wBAAwB,CAC5B,gBAC0C;AAC1C,SAAO,gBAAgB;AAAA,IACrB,CAAC,UAAU,WAAW;AACpB,YAAM,QAAQ,YAAY,OAAO,GAAG;AACpC,UAAI,CAAC,MAAO,QAAO;AAEnB,YAAM,UAAU,mBAAmB;AAAA,QACjC,MAAM,OAAO,MAAM,KAAK;AAAA,QACxB,KAAK,OAAO,IAAI,KAAK;AAAA,MAAA,CACtB;AAED,eAAS,OAAO,GAAG,IAAI;AACvB,aAAO;AAAA,IACT;AAAA,IACA,CAAA;AAAA,EAAC;AAEL;AAaA,MAAM,uBAAuB,CAC3B,OACA,YACiB;AACjB,QAAM,EAAE,cAAc,MAAA,IAAU;AAGhC,QAAM,WAAW,cAAc,KAAK;AACpC,QAAM,aAAa,gBAAgB,KAAK;AACxC,QAAM,WAAW,uBAAuB,KAAK;AAC7C,QAAM,aAAa,kBAAkB,KAAK;AAC1C,QAAM,YAAY,iBAAiB,OAAO,QAAQ;AAClD,QAAM,WAAW,gBAAgB,KAAK;AACtC,QAAM,SAAS,cAAc,KAAK;AAGlC,QAAM,OAAO,kBAAkB,UAAU,UAAU;AACvC,mBAAiB,QAAQ;AACrC,QAAM,oBAAoB,yBAAyB,UAAU;AAC7D,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA,oBAAoB,QAAQ;AAAA,EAAA;AAE9B,QAAM,kBAAkB,sBAAsB,QAAQ;AACtD,QAAM,gBAAgB,uBAAuB,MAAM;AAEnD,SAAO,OAAO,MAAM;AAAA,IAClB;AAAA,IACA,SAAS;AAAA,IACT,SAAS;AAAA,IACT,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,EAAA,CACD;AACH;AASA,MAAM,sBAAsB,CAC1B,OACA,YACiB;AACjB,QAAM,EAAE,cAAc,MAAA,IAAU;AAGhC,QAAM,WAAW,cAAc,KAAK;AACpC,QAAM,aAAa,gBAAgB,KAAK;AACxC,QAAM,WAAW,uBAAuB,KAAK;AAC7C,QAAM,aAAa,kBAAkB,KAAK;AAC1C,QAAM,YAAY,iBAAiB,OAAO,QAAQ;AAClD,QAAM,WAAW,gBAAgB,KAAK;AACtC,QAAM,SAAS,cAAc,KAAK;AAGlC,QAAM,OAAO,kBAAkB,UAAU,UAAU;AACvC,mBAAiB,QAAQ;AACrC,QAAM,oBAAoB,yBAAyB,UAAU;AAC7D,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA,oBAAoB,QAAQ;AAAA,EAAA;AAE9B,QAAM,kBAAkB,sBAAsB,QAAQ;AACtD,QAAM,gBAAgB,uBAAuB,MAAM;AAEnD,SAAO,OAAO,KAAK;AAAA,IACjB;AAAA,IACA,SAAS;AAAA,IACT,iBAAiB;AAAA,IACjB,SAAS;AAAA,IACT;AAAA,IACA;AAAA,EAAA,CACD;AACH;AASA,MAAM,yBAAyB,CAC7B,OACA,YACiB;AACjB,QAAM,EAAE,cAAc,MAAA,IAAU;AAGhC,QAAM,WAAW,cAAc,KAAK;AACpC,QAAM,aAAa,gBAAgB,KAAK;AACxC,QAAM,WAAW,uBAAuB,KAAK;AAC7C,QAAM,aAAa,kBAAkB,KAAK;AAC1C,QAAM,YAAY,iBAAiB,OAAO,QAAQ;AAClD,QAAM,cAAc,mBAAmB,KAAK;AAC5C,QAAM,WAAW,gBAAgB,KAAK;AACtC,QAAM,SAAS,cAAc,KAAK;AAGlC,QAAM,OAAO,kBAAkB,UAAU,UAAU;AACvC,mBAAiB,QAAQ;AACrC,QAAM,oBAAoB,yBAAyB,UAAU;AAC7D,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA,oBAAoB,QAAQ;AAAA,EAAA;AAE9B,QAAM,kBAAkB,sBAAsB,WAAW;AACzD,QAAM,kBAAkB,sBAAsB,QAAQ;AACtD,QAAM,gBAAgB,uBAAuB,MAAM;AAEnD,SAAO,OAAO,QAAQ;AAAA,IACpB;AAAA,IACA,SAAS;AAAA,IACT,iBAAiB;AAAA,IACjB,SAAS;AAAA,IACT;AAAA,IACA,GAAG;AAAA,IACH;AAAA,EAAA,CACD;AACH;AASA,MAAM,yBAAyB,CAC7B,OACA,YACiB;AACjB,QAAM,EAAE,cAAc,MAAA,IAAU;AAGhC,QAAM,WAAW,cAAc,KAAK;AACpC,QAAM,aAAa,gBAAgB,KAAK;AACvB,yBAAuB,KAAK;AAC7C,QAAM,aAAa,kBAAkB,KAAK;AAC1C,QAAM,SAAS,cAAc,KAAK;AAClC,QAAM,YAAY,iBAAiB,OAAO,QAAQ;AAGlD,QAAM,WAAW,kBAAkB,UAAU,UAAU;AACvD,QAAM,gBAAgB,uBAAuB,MAAM;AACnD,QAAM,oBAAoB,yBAAyB,UAAU;AAC7D,QAAM,kBAAkB;AAAA,IACtB;AAAA,IACA;AAAA,IACA,oBAAoB,QAAQ;AAAA,EAAA;AAG9B,SAAO,KAAK,QAAQ,MAAM;AAAA,IACxB,SAAS;AAAA,IACT;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EAAA,CACD;AACH;AAmBO,MAAM,sBAAsB,CACjC,OACA,aACA,cAAuB,UACpB;AAEH,QAAM,WAAW,cAAc,KAAK;AACpC,QAAM,aAAa,gBAAgB,KAAK;AACxC,QAAM,WAAW,uBAAuB,KAAK;AAC7C,QAAM,aAAa,kBAAkB,KAAK;AAC1C,QAAM,YAAY,iBAAiB,OAAO,QAAQ;AAClD,QAAM,cAAc,mBAAmB,KAAkB;AACzD,QAAM,WAAW,gBAAgB,KAAK;AACtC,QAAM,SAAS,cAAc,KAAK;AAGlC,QAAM,OAAO,kBAAkB,UAAU,YAAY,IAAI;AAC7C,mBAAiB,QAAQ;AACrC,QAAM,oBAAoB,yBAAyB,UAAU;AAC7D,QAAM,QAAQ,mBAAmB,SAAS;AAC1C,QAAM,qBAAqB,yBAAyB,WAAW;AAC/D,QAAM,kBAAkB,sBAAsB,QAAQ;AACtD,QAAM,gBAAgB,uBAAuB,MAAM;AAEnD,SAAO;AAAA,IACL;AAAA,IACA,eAAe;AAAA,IACf,UAAU;AAAA,IACV,SAAS;AAAA,IACT,OAAO;AAAA,IACP,SAAS;AAAA,IACT,mBAAmB;AAAA,IACnB;AAAA,IACA,aAAa;AAAA,IACb;AAAA,EAAA;AAEJ;AAqBO,MAAM,yBAAuD;AAAA,EAClE,YAAY;AAAA,EAEZ,gBAAgB,CACd,OACA,YACiB;AACjB,UAAM,EAAE,YAAY,OAAO,WAAW,YAAY;AAGlD,QAAI,aAAa,MAAM,WAAW,CAAC,GAAG,KAAK;AACzC,aAAO,uBAAuB,OAAO,OAAO;AAAA,IAC9C;AAGA,YAAQ,UAAA;AAAA,MACN,KAAK;AACH,eAAO,oBAAoB,OAAO,OAAO;AAAA,MAC3C,KAAK;AACH,eAAO,uBAAuB,OAAO,OAAO;AAAA,MAC9C;AACE,eAAO,qBAAqB,OAAO,OAAO;AAAA,IAAA;AAAA,EAEhD;AACF;"}
@@ -1,4 +1,4 @@
1
1
  import { ExpertEntry } from '../../types/data';
2
- export declare const EXPERTS_QUERY = "\n query getExpertsContent($limit: Int, $offset: Int, $ids: [QueryArgument], $relatedTo: [QueryArgument], $isMediaTrained: Boolean, $orderBy: String, $fixedOrder: Boolean) {\n entryCount(\n section: \"experts\"\n limit: $limit\n offset: $offset\n type: \"expertsContent\"\n id: $ids\n relatedTo: $relatedTo\n expertsMediaTrained: $isMediaTrained\n )\n entries(\n section: \"experts\"\n limit: $limit\n offset: $offset\n type: \"expertsContent\"\n id: $ids\n relatedTo: $relatedTo\n expertsMediaTrained: $isMediaTrained\n orderBy: $orderBy\n fixedOrder: $fixedOrder\n ) {\n ...EntriesNativeFields\n ... on expertsContent_Entry {\n ...Content\n ...Categories\n }\n }\n }\n \n fragment EntriesNativeFields on EntryInterface {\n id\n uid\n status\n enabled\n slug\n title\n type: typeHandle\n postDate\n expiryDate\n dateUpdated\n dateCreated\n }\n\n \n fragment CategoryFields on CategoryInterface {\n title\n url\n id\n }\n\n \n fragment Content on expertsContent_Entry {\n ...Name\n ...Imagery\n ...Biography\n organizations: expertsOrganization {\n ...Organizations\n }\n ...Contact\n ...Skills\n }\n\n \n fragment Categories on expertsContent_Entry {\n areasOfExpertise: expertsCategoryAreaOfExpertise {\n ...CategoryFields\n }\n topics: expertsCategoryTopics {\n ...CategoryFields\n }\n }\n\n \n fragment Name on expertsContent_Entry {\n prefix: expertsNamePrefix\n firstName: expertsNameFirst\n middleName: expertsNameMiddle\n lastName: expertsNameLast\n suffix: expertsNameSuffix\n pronouns: expertsNamePronouns\n }\n\n \n fragment Imagery on expertsContent_Entry {\n headshot: expertsImageHeadShot {\n ... on experts_Asset {\n url\n }\n }\n }\n\n \n fragment Biography on expertsContent_Entry {\n summary: expertsSummary {\n html\n }\n bio: expertsBiography {\n html\n }\n }\n\n \n fragment Organizations on expertsOrganization_Entry {\n id\n title\n url\n jobs: expertsOrganizationJob {\n ...Jobs\n }\n }\n\n \n fragment Jobs on expertsOrganizationJob_Entry {\n id\n url\n title: expertsOrganizationJobTitle\n roomNumber: expertsOrganizationJobRoomNumber\n campusUnits: categoryCampusUnit {\n title\n ... on campusUnits_Category {\n link: externalLink {\n url\n }\n }\n }\n }\n\n \n fragment Contact on expertsContent_Entry {\n email: expertsContactEmail\n linkedin: expertsContactLinkedin\n website: expertsContactWebsite\n twitter: expertsContactTwitter\n blueSky: expertsContactBluesky {\n id\n url\n }\n substack: expertsContactSubstack {\n id\n url\n }\n }\n\n \n fragment Skills on expertsContent_Entry {\n languages: expertsLanguages\n mediaTrained: expertsMediaTrained\n }\n\n";
2
+ export declare const EXPERTS_QUERY = "\n query getExpertsContent($limit: Int, $offset: Int, $ids: [QueryArgument], $relatedTo: [QueryArgument], $isMediaTrained: Boolean, $orderBy: String, $fixedOrder: Boolean) {\n entryCount(\n section: \"experts\"\n limit: $limit\n offset: $offset\n type: \"expertsContent\"\n id: $ids\n relatedTo: $relatedTo\n expertsMediaTrained: $isMediaTrained\n )\n entries(\n section: \"experts\"\n limit: $limit\n offset: $offset\n type: \"expertsContent\"\n id: $ids\n relatedTo: $relatedTo\n expertsMediaTrained: $isMediaTrained\n orderBy: $orderBy\n fixedOrder: $fixedOrder\n ) {\n ...EntriesNativeFields\n ... on expertsContent_Entry {\n ...Content\n ...Categories\n }\n }\n }\n \n fragment EntriesNativeFields on EntryInterface {\n id\n uid\n status\n enabled\n slug\n title\n type: typeHandle\n postDate\n expiryDate\n dateUpdated\n dateCreated\n }\n\n \n fragment CategoryFields on CategoryInterface {\n title\n url\n id\n }\n\n \n fragment Content on expertsContent_Entry {\n ...Name\n ...Imagery\n ...Biography\n organizations: expertsOrganization {\n ...Organizations\n }\n ...Contact\n ...Skills\n }\n\n \n fragment Categories on expertsContent_Entry {\n areasOfExpertise: expertsCategoryAreaOfExpertise {\n ...CategoryFields\n }\n topics: expertsCategoryTopics {\n ...CategoryFields\n }\n }\n\n \n fragment Name on expertsContent_Entry {\n prefix: expertsNamePrefix\n firstName: expertsNameFirst\n middleName: expertsNameMiddle\n lastName: expertsNameLast\n suffix: expertsNameSuffix\n pronouns: expertsNamePronouns\n }\n\n \n fragment Imagery on expertsContent_Entry {\n headshot: expertsImageHeadShot {\n ... on experts_Asset {\n url\n }\n }\n }\n\n \n fragment Biography on expertsContent_Entry {\n summary: expertsSummary {\n html\n }\n bio: expertsBiography {\n html\n }\n }\n\n \n fragment Organizations on expertsOrganization_Entry {\n id\n title\n url\n jobs: expertsOrganizationJob {\n ...Jobs\n }\n }\n\n \n fragment Jobs on expertsOrganizationJob_Entry {\n id\n url\n title: expertsOrganizationJobTitle\n roomNumber: expertsOrganizationJobRoomNumber\n collegeSchools: categoryCollegeSchools {\n title\n ... on collegeSchools_Category {\n link: externalLink {\n url\n }\n }\n }\n campusUnits: categoryCampusUnit {\n title\n ... on campusUnits_Category {\n link: externalLink {\n url\n }\n }\n }\n }\n\n \n fragment Contact on expertsContent_Entry {\n email: expertsContactEmail\n linkedin: expertsContactLinkedin\n website: expertsContactWebsite\n twitter: expertsContactTwitter\n blueSky: expertsContactBluesky {\n id\n url\n }\n substack: expertsContactSubstack {\n id\n url\n }\n }\n\n \n fragment Skills on expertsContent_Entry {\n languages: expertsLanguages\n mediaTrained: expertsMediaTrained\n }\n\n";
3
3
  export declare const expertsFetchStrategy: import('../../factory').FetchStrategy<ExpertEntry, any>;
4
4
  //# sourceMappingURL=experts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"experts.d.ts","sourceRoot":"","sources":["../../../source/strategies/fetch/experts.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAkJzC,eAAO,MAAM,aAAa,y9FAwCzB,CAAC;AA+BF,eAAO,MAAM,oBAAoB,yDA4C/B,CAAC"}
1
+ {"version":3,"file":"experts.d.ts","sourceRoot":"","sources":["../../../source/strategies/fetch/experts.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA0JzC,eAAO,MAAM,aAAa,ooGAwCzB,CAAC;AA+BF,eAAO,MAAM,oBAAoB,yDA4C/B,CAAC"}
@@ -27,6 +27,14 @@ const FRAGMENT_EXPERT_JOBS = `
27
27
  url
28
28
  title: expertsOrganizationJobTitle
29
29
  roomNumber: expertsOrganizationJobRoomNumber
30
+ collegeSchools: categoryCollegeSchools {
31
+ title
32
+ ... on collegeSchools_Category {
33
+ link: externalLink {
34
+ url
35
+ }
36
+ }
37
+ }
30
38
  campusUnits: categoryCampusUnit {
31
39
  title
32
40
  ... on campusUnits_Category {
@@ -1 +1 @@
1
- {"version":3,"file":"experts.js","sources":["../../../source/strategies/fetch/experts.ts"],"sourcesContent":["/**\n * Experts Fetch Strategy\n *\n * Strategy for fetching expert profile data from the UMD Experts GraphQL API.\n *\n * @module strategies/fetch/experts\n */\n\nimport { createGraphQLFetchStrategy } from './graphql';\nimport { ExpertEntry } from 'types/data';\n\n/**\n * GraphQL fragments for expert data\n */\nconst FRAGMENT_CATEGORIES_NATIVE_FIELDS = `\n fragment CategoryFields on CategoryInterface {\n title\n url\n id\n }\n`;\n\nconst FRAGMENT_ENTRIES_NATIVE_FIELDS = `\n fragment EntriesNativeFields on EntryInterface {\n id\n uid\n status\n enabled\n slug\n title\n type: typeHandle\n postDate\n expiryDate\n dateUpdated\n dateCreated\n }\n`;\n\nconst FRAGMENT_EXPERT_JOBS = `\n fragment Jobs on expertsOrganizationJob_Entry {\n id\n url\n title: expertsOrganizationJobTitle\n roomNumber: expertsOrganizationJobRoomNumber\n campusUnits: categoryCampusUnit {\n title\n ... on campusUnits_Category {\n link: externalLink {\n url\n }\n }\n }\n }\n`;\n\nconst FRAGMENT_EXPERT_NAME = `\n fragment Name on expertsContent_Entry {\n prefix: expertsNamePrefix\n firstName: expertsNameFirst\n middleName: expertsNameMiddle\n lastName: expertsNameLast\n suffix: expertsNameSuffix\n pronouns: expertsNamePronouns\n }\n`;\n\nconst FRAGMENT_EXPERT_IMAGERY = `\n fragment Imagery on expertsContent_Entry {\n headshot: expertsImageHeadShot {\n ... on experts_Asset {\n url\n }\n }\n }\n`;\n\nconst FRAGMENT_EXPERT_BIOGRAPHY = `\n fragment Biography on expertsContent_Entry {\n summary: expertsSummary {\n html\n }\n bio: expertsBiography {\n html\n }\n }\n`;\n\nconst FRAGMENT_ENTRIES_ORGANIZATIONS = `\n fragment Organizations on expertsOrganization_Entry {\n id\n title\n url\n jobs: expertsOrganizationJob {\n ...Jobs\n }\n }\n`;\n\nconst FRAGMENT_EXPERT_CONTACT = `\n fragment Contact on expertsContent_Entry {\n email: expertsContactEmail\n linkedin: expertsContactLinkedin\n website: expertsContactWebsite\n twitter: expertsContactTwitter\n blueSky: expertsContactBluesky {\n id\n url\n }\n substack: expertsContactSubstack {\n id\n url\n }\n }\n`;\n\nconst FRAGMENT_EXPERT_SKILLS = `\n fragment Skills on expertsContent_Entry {\n languages: expertsLanguages\n mediaTrained: expertsMediaTrained\n }\n`;\n\nconst FRAGMENT_EXPERT_CONTENT = `\n fragment Content on expertsContent_Entry {\n ...Name\n ...Imagery\n ...Biography\n organizations: expertsOrganization {\n ...Organizations\n }\n ...Contact\n ...Skills\n }\n`;\n\nconst FRAGMENT_EXPERT_CATEGORIES = `\n fragment Categories on expertsContent_Entry {\n areasOfExpertise: expertsCategoryAreaOfExpertise {\n ...CategoryFields\n }\n topics: expertsCategoryTopics {\n ...CategoryFields\n }\n }\n`;\n\n/**\n * GraphQL query for experts\n * Supports filtering by ID, relatedTo (categories), or fetching all experts\n *\n * @param ids - Specific entry IDs to fetch (use with fixedOrder for preserved order)\n * @param relatedTo - Category IDs to filter by (narrows results to related entries)\n * @param fixedOrder - When true, returns results in the order specified by ids argument\n * @param orderBy - Field to order results by (ignored when fixedOrder is true)\n */\nexport const EXPERTS_QUERY = `\n query getExpertsContent($limit: Int, $offset: Int, $ids: [QueryArgument], $relatedTo: [QueryArgument], $isMediaTrained: Boolean, $orderBy: String, $fixedOrder: Boolean) {\n entryCount(\n section: \"experts\"\n limit: $limit\n offset: $offset\n type: \"expertsContent\"\n id: $ids\n relatedTo: $relatedTo\n expertsMediaTrained: $isMediaTrained\n )\n entries(\n section: \"experts\"\n limit: $limit\n offset: $offset\n type: \"expertsContent\"\n id: $ids\n relatedTo: $relatedTo\n expertsMediaTrained: $isMediaTrained\n orderBy: $orderBy\n fixedOrder: $fixedOrder\n ) {\n ...EntriesNativeFields\n ... on expertsContent_Entry {\n ...Content\n ...Categories\n }\n }\n }\n ${FRAGMENT_ENTRIES_NATIVE_FIELDS}\n ${FRAGMENT_CATEGORIES_NATIVE_FIELDS}\n ${FRAGMENT_EXPERT_CONTENT}\n ${FRAGMENT_EXPERT_CATEGORIES}\n ${FRAGMENT_EXPERT_NAME}\n ${FRAGMENT_EXPERT_IMAGERY}\n ${FRAGMENT_EXPERT_BIOGRAPHY}\n ${FRAGMENT_ENTRIES_ORGANIZATIONS}\n ${FRAGMENT_EXPERT_JOBS}\n ${FRAGMENT_EXPERT_CONTACT}\n ${FRAGMENT_EXPERT_SKILLS}\n`;\n\n/**\n * Experts fetch strategy\n *\n * Fetches expert profile data from the UMD Experts GraphQL API.\n *\n * Parameter handling:\n * - `ids` - Specific entry IDs to fetch. When provided, uses `fixedOrder: true`\n * to return results in the exact order specified by the IDs array.\n * - `categories` - Category IDs passed to `relatedTo` to narrow results to\n * entries related to those categories.\n * - `isMediaTrained` - Filter for media-trained experts only.\n *\n * @example\n * ```typescript\n * // Fetch by categories (uses relatedTo)\n * const feed = createBaseFeed({\n * token: 'my-token',\n * fetchStrategy: expertsFetchStrategy,\n * categories: ['computer-science', 'engineering'],\n * });\n *\n * // Fetch specific IDs in order (uses fixedOrder)\n * const feed = createBaseFeed({\n * token: 'my-token',\n * fetchStrategy: expertsFetchStrategy,\n * ids: ['123', '456', '789'], // Results returned in this order\n * });\n * ```\n */\nexport const expertsFetchStrategy = createGraphQLFetchStrategy<ExpertEntry>({\n endpoint: 'https://content-api.umd.edu/graphql',\n\n queries: {\n entries: EXPERTS_QUERY,\n },\n\n transformResponse: (data) => {\n if (!data || !data.data || !data.data.entries) {\n return null;\n }\n return data.data.entries || null;\n },\n\n transformCount: (data) => {\n if (!data || !data.data) {\n return 0;\n }\n return data.data.entryCount || 0;\n },\n\n composeVariables: (baseVariables) => {\n const { categories, entriesToRemove, ids, isMediaTrained, ...rest } =\n baseVariables;\n\n const variables: any = { ...rest };\n\n if (ids) {\n variables.ids = Array.isArray(ids) ? ids : [ids];\n variables.fixedOrder = true;\n } else {\n variables.orderBy = 'expertsNameLast';\n }\n\n if (categories) {\n variables.relatedTo = categories;\n }\n\n if (isMediaTrained) {\n variables.isMediaTrained = isMediaTrained;\n }\n\n return variables;\n },\n});\n"],"names":[],"mappings":";AAcA,MAAM,oCAAoC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQ1C,MAAM,iCAAiC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBvC,MAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiB7B,MAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAW7B,MAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUhC,MAAM,4BAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWlC,MAAM,iCAAiC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWvC,MAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBhC,MAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAO/B,MAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAahC,MAAM,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoB5B,MAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA6BzB,8BAA8B;AAAA,IAC9B,iCAAiC;AAAA,IACjC,uBAAuB;AAAA,IACvB,0BAA0B;AAAA,IAC1B,oBAAoB;AAAA,IACpB,uBAAuB;AAAA,IACvB,yBAAyB;AAAA,IACzB,8BAA8B;AAAA,IAC9B,oBAAoB;AAAA,IACpB,uBAAuB;AAAA,IACvB,sBAAsB;AAAA;AAgCnB,MAAM,uBAAuB,2BAAwC;AAAA,EAC1E,UAAU;AAAA,EAEV,SAAS;AAAA,IACP,SAAS;AAAA,EAAA;AAAA,EAGX,mBAAmB,CAAC,SAAS;AAC3B,QAAI,CAAC,QAAQ,CAAC,KAAK,QAAQ,CAAC,KAAK,KAAK,SAAS;AAC7C,aAAO;AAAA,IACT;AACA,WAAO,KAAK,KAAK,WAAW;AAAA,EAC9B;AAAA,EAEA,gBAAgB,CAAC,SAAS;AACxB,QAAI,CAAC,QAAQ,CAAC,KAAK,MAAM;AACvB,aAAO;AAAA,IACT;AACA,WAAO,KAAK,KAAK,cAAc;AAAA,EACjC;AAAA,EAEA,kBAAkB,CAAC,kBAAkB;AACnC,UAAM,EAAE,YAAY,iBAAiB,KAAK,gBAAgB,GAAG,SAC3D;AAEF,UAAM,YAAiB,EAAE,GAAG,KAAA;AAE5B,QAAI,KAAK;AACP,gBAAU,MAAM,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG;AAC/C,gBAAU,aAAa;AAAA,IACzB,OAAO;AACL,gBAAU,UAAU;AAAA,IACtB;AAEA,QAAI,YAAY;AACd,gBAAU,YAAY;AAAA,IACxB;AAEA,QAAI,gBAAgB;AAClB,gBAAU,iBAAiB;AAAA,IAC7B;AAEA,WAAO;AAAA,EACT;AACF,CAAC;"}
1
+ {"version":3,"file":"experts.js","sources":["../../../source/strategies/fetch/experts.ts"],"sourcesContent":["/**\n * Experts Fetch Strategy\n *\n * Strategy for fetching expert profile data from the UMD Experts GraphQL API.\n *\n * @module strategies/fetch/experts\n */\n\nimport { createGraphQLFetchStrategy } from './graphql';\nimport { ExpertEntry } from 'types/data';\n\n/**\n * GraphQL fragments for expert data\n */\nconst FRAGMENT_CATEGORIES_NATIVE_FIELDS = `\n fragment CategoryFields on CategoryInterface {\n title\n url\n id\n }\n`;\n\nconst FRAGMENT_ENTRIES_NATIVE_FIELDS = `\n fragment EntriesNativeFields on EntryInterface {\n id\n uid\n status\n enabled\n slug\n title\n type: typeHandle\n postDate\n expiryDate\n dateUpdated\n dateCreated\n }\n`;\n\nconst FRAGMENT_EXPERT_JOBS = `\n fragment Jobs on expertsOrganizationJob_Entry {\n id\n url\n title: expertsOrganizationJobTitle\n roomNumber: expertsOrganizationJobRoomNumber\n collegeSchools: categoryCollegeSchools {\n title\n ... on collegeSchools_Category {\n link: externalLink {\n url\n }\n }\n }\n campusUnits: categoryCampusUnit {\n title\n ... on campusUnits_Category {\n link: externalLink {\n url\n }\n }\n }\n }\n`;\n\nconst FRAGMENT_EXPERT_NAME = `\n fragment Name on expertsContent_Entry {\n prefix: expertsNamePrefix\n firstName: expertsNameFirst\n middleName: expertsNameMiddle\n lastName: expertsNameLast\n suffix: expertsNameSuffix\n pronouns: expertsNamePronouns\n }\n`;\n\nconst FRAGMENT_EXPERT_IMAGERY = `\n fragment Imagery on expertsContent_Entry {\n headshot: expertsImageHeadShot {\n ... on experts_Asset {\n url\n }\n }\n }\n`;\n\nconst FRAGMENT_EXPERT_BIOGRAPHY = `\n fragment Biography on expertsContent_Entry {\n summary: expertsSummary {\n html\n }\n bio: expertsBiography {\n html\n }\n }\n`;\n\nconst FRAGMENT_ENTRIES_ORGANIZATIONS = `\n fragment Organizations on expertsOrganization_Entry {\n id\n title\n url\n jobs: expertsOrganizationJob {\n ...Jobs\n }\n }\n`;\n\nconst FRAGMENT_EXPERT_CONTACT = `\n fragment Contact on expertsContent_Entry {\n email: expertsContactEmail\n linkedin: expertsContactLinkedin\n website: expertsContactWebsite\n twitter: expertsContactTwitter\n blueSky: expertsContactBluesky {\n id\n url\n }\n substack: expertsContactSubstack {\n id\n url\n }\n }\n`;\n\nconst FRAGMENT_EXPERT_SKILLS = `\n fragment Skills on expertsContent_Entry {\n languages: expertsLanguages\n mediaTrained: expertsMediaTrained\n }\n`;\n\nconst FRAGMENT_EXPERT_CONTENT = `\n fragment Content on expertsContent_Entry {\n ...Name\n ...Imagery\n ...Biography\n organizations: expertsOrganization {\n ...Organizations\n }\n ...Contact\n ...Skills\n }\n`;\n\nconst FRAGMENT_EXPERT_CATEGORIES = `\n fragment Categories on expertsContent_Entry {\n areasOfExpertise: expertsCategoryAreaOfExpertise {\n ...CategoryFields\n }\n topics: expertsCategoryTopics {\n ...CategoryFields\n }\n }\n`;\n\n/**\n * GraphQL query for experts\n * Supports filtering by ID, relatedTo (categories), or fetching all experts\n *\n * @param ids - Specific entry IDs to fetch (use with fixedOrder for preserved order)\n * @param relatedTo - Category IDs to filter by (narrows results to related entries)\n * @param fixedOrder - When true, returns results in the order specified by ids argument\n * @param orderBy - Field to order results by (ignored when fixedOrder is true)\n */\nexport const EXPERTS_QUERY = `\n query getExpertsContent($limit: Int, $offset: Int, $ids: [QueryArgument], $relatedTo: [QueryArgument], $isMediaTrained: Boolean, $orderBy: String, $fixedOrder: Boolean) {\n entryCount(\n section: \"experts\"\n limit: $limit\n offset: $offset\n type: \"expertsContent\"\n id: $ids\n relatedTo: $relatedTo\n expertsMediaTrained: $isMediaTrained\n )\n entries(\n section: \"experts\"\n limit: $limit\n offset: $offset\n type: \"expertsContent\"\n id: $ids\n relatedTo: $relatedTo\n expertsMediaTrained: $isMediaTrained\n orderBy: $orderBy\n fixedOrder: $fixedOrder\n ) {\n ...EntriesNativeFields\n ... on expertsContent_Entry {\n ...Content\n ...Categories\n }\n }\n }\n ${FRAGMENT_ENTRIES_NATIVE_FIELDS}\n ${FRAGMENT_CATEGORIES_NATIVE_FIELDS}\n ${FRAGMENT_EXPERT_CONTENT}\n ${FRAGMENT_EXPERT_CATEGORIES}\n ${FRAGMENT_EXPERT_NAME}\n ${FRAGMENT_EXPERT_IMAGERY}\n ${FRAGMENT_EXPERT_BIOGRAPHY}\n ${FRAGMENT_ENTRIES_ORGANIZATIONS}\n ${FRAGMENT_EXPERT_JOBS}\n ${FRAGMENT_EXPERT_CONTACT}\n ${FRAGMENT_EXPERT_SKILLS}\n`;\n\n/**\n * Experts fetch strategy\n *\n * Fetches expert profile data from the UMD Experts GraphQL API.\n *\n * Parameter handling:\n * - `ids` - Specific entry IDs to fetch. When provided, uses `fixedOrder: true`\n * to return results in the exact order specified by the IDs array.\n * - `categories` - Category IDs passed to `relatedTo` to narrow results to\n * entries related to those categories.\n * - `isMediaTrained` - Filter for media-trained experts only.\n *\n * @example\n * ```typescript\n * // Fetch by categories (uses relatedTo)\n * const feed = createBaseFeed({\n * token: 'my-token',\n * fetchStrategy: expertsFetchStrategy,\n * categories: ['computer-science', 'engineering'],\n * });\n *\n * // Fetch specific IDs in order (uses fixedOrder)\n * const feed = createBaseFeed({\n * token: 'my-token',\n * fetchStrategy: expertsFetchStrategy,\n * ids: ['123', '456', '789'], // Results returned in this order\n * });\n * ```\n */\nexport const expertsFetchStrategy = createGraphQLFetchStrategy<ExpertEntry>({\n endpoint: 'https://content-api.umd.edu/graphql',\n\n queries: {\n entries: EXPERTS_QUERY,\n },\n\n transformResponse: (data) => {\n if (!data || !data.data || !data.data.entries) {\n return null;\n }\n return data.data.entries || null;\n },\n\n transformCount: (data) => {\n if (!data || !data.data) {\n return 0;\n }\n return data.data.entryCount || 0;\n },\n\n composeVariables: (baseVariables) => {\n const { categories, entriesToRemove, ids, isMediaTrained, ...rest } =\n baseVariables;\n\n const variables: any = { ...rest };\n\n if (ids) {\n variables.ids = Array.isArray(ids) ? ids : [ids];\n variables.fixedOrder = true;\n } else {\n variables.orderBy = 'expertsNameLast';\n }\n\n if (categories) {\n variables.relatedTo = categories;\n }\n\n if (isMediaTrained) {\n variables.isMediaTrained = isMediaTrained;\n }\n\n return variables;\n },\n});\n"],"names":[],"mappings":";AAcA,MAAM,oCAAoC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQ1C,MAAM,iCAAiC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBvC,MAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyB7B,MAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAW7B,MAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUhC,MAAM,4BAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWlC,MAAM,iCAAiC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWvC,MAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBhC,MAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAO/B,MAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAahC,MAAM,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoB5B,MAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA6BzB,8BAA8B;AAAA,IAC9B,iCAAiC;AAAA,IACjC,uBAAuB;AAAA,IACvB,0BAA0B;AAAA,IAC1B,oBAAoB;AAAA,IACpB,uBAAuB;AAAA,IACvB,yBAAyB;AAAA,IACzB,8BAA8B;AAAA,IAC9B,oBAAoB;AAAA,IACpB,uBAAuB;AAAA,IACvB,sBAAsB;AAAA;AAgCnB,MAAM,uBAAuB,2BAAwC;AAAA,EAC1E,UAAU;AAAA,EAEV,SAAS;AAAA,IACP,SAAS;AAAA,EAAA;AAAA,EAGX,mBAAmB,CAAC,SAAS;AAC3B,QAAI,CAAC,QAAQ,CAAC,KAAK,QAAQ,CAAC,KAAK,KAAK,SAAS;AAC7C,aAAO;AAAA,IACT;AACA,WAAO,KAAK,KAAK,WAAW;AAAA,EAC9B;AAAA,EAEA,gBAAgB,CAAC,SAAS;AACxB,QAAI,CAAC,QAAQ,CAAC,KAAK,MAAM;AACvB,aAAO;AAAA,IACT;AACA,WAAO,KAAK,KAAK,cAAc;AAAA,EACjC;AAAA,EAEA,kBAAkB,CAAC,kBAAkB;AACnC,UAAM,EAAE,YAAY,iBAAiB,KAAK,gBAAgB,GAAG,SAC3D;AAEF,UAAM,YAAiB,EAAE,GAAG,KAAA;AAE5B,QAAI,KAAK;AACP,gBAAU,MAAM,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG;AAC/C,gBAAU,aAAa;AAAA,IACzB,OAAO;AACL,gBAAU,UAAU;AAAA,IACtB;AAEA,QAAI,YAAY;AACd,gBAAU,YAAY;AAAA,IACxB;AAEA,QAAI,gBAAgB;AAClB,gBAAU,iBAAiB;AAAA,IAC7B;AAEA,WAAO;AAAA,EACT;AACF,CAAC;"}
@@ -1 +1 @@
1
- {"version":3,"file":"graphql.d.ts","sourceRoot":"","sources":["../../../source/strategies/fetch/graphql.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAKzD,MAAM,WAAW,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,GAAG;IAExD,QAAQ,EAAE,MAAM,CAAC;IAGjB,OAAO,EAAE;QAEP,OAAO,EAAE,MAAM,CAAC;QAEhB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IAGF,iBAAiB,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,KAAK,EAAE,GAAG,IAAI,CAAC;IAGvD,cAAc,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,MAAM,CAAC;IAG5C,gBAAgB,CAAC,EAAE,CAAC,aAAa,EAAE,GAAG,KAAK,GAAG,CAAC;CAChD;AA+BD,wBAAgB,0BAA0B,CACxC,KAAK,EACL,SAAS,GAAG,GAAG,EACf,UAAU,GAAG,GAAG,EAEhB,MAAM,EAAE,kBAAkB,CAAC,KAAK,EAAE,SAAS,CAAC,GAC3C,aAAa,CAAC,KAAK,EAAE,UAAU,CAAC,CAkIlC"}
1
+ {"version":3,"file":"graphql.d.ts","sourceRoot":"","sources":["../../../source/strategies/fetch/graphql.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAKzD,MAAM,WAAW,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,GAAG;IAExD,QAAQ,EAAE,MAAM,CAAC;IAGjB,OAAO,EAAE;QAEP,OAAO,EAAE,MAAM,CAAC;QAEhB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IAGF,iBAAiB,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,KAAK,EAAE,GAAG,IAAI,CAAC;IAGvD,cAAc,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,MAAM,CAAC;IAG5C,gBAAgB,CAAC,EAAE,CAAC,aAAa,EAAE,GAAG,KAAK,GAAG,CAAC;CAChD;AA+BD,wBAAgB,0BAA0B,CACxC,KAAK,EACL,SAAS,GAAG,GAAG,EACf,UAAU,GAAG,GAAG,EAEhB,MAAM,EAAE,kBAAkB,CAAC,KAAK,EAAE,SAAS,CAAC,GAC3C,aAAa,CAAC,KAAK,EAAE,UAAU,CAAC,CAyHlC"}
@@ -67,7 +67,8 @@ function createGraphQLFetchStrategy(config) {
67
67
  getOffset,
68
68
  entriesToRemove,
69
69
  ids,
70
- isMediaTrained
70
+ isLazyLoad,
71
+ ...extraProps
71
72
  } = props;
72
73
  const baseVariables = {
73
74
  token,
@@ -83,12 +84,7 @@ function createGraphQLFetchStrategy(config) {
83
84
  if (ids) {
84
85
  baseVariables.ids = ids;
85
86
  }
86
- if (isMediaTrained !== void 0) {
87
- baseVariables.isMediaTrained = isMediaTrained;
88
- }
89
- if (props.expertId) {
90
- baseVariables.expertId = props.expertId;
91
- }
87
+ Object.assign(baseVariables, extraProps);
92
88
  if (composeVariables) {
93
89
  return composeVariables(baseVariables);
94
90
  }
@@ -1 +1 @@
1
- {"version":3,"file":"graphql.js","sources":["../../../source/strategies/fetch/graphql.ts"],"sourcesContent":["/**\n * GraphQL Fetch Strategy Factory\n *\n * Generic factory for creating GraphQL-based fetch strategies.\n * Handles common GraphQL patterns: queries, variables, transformations.\n *\n * @module strategies/fetch/graphql\n */\n\nimport {\n fetchGraphQL,\n GraphQLVariables,\n} from '@universityofmaryland/web-utilities-library/network';\nimport { FetchStrategy } from '../../factory/core/types';\n\n/**\n * Configuration for creating a GraphQL fetch strategy\n */\nexport interface GraphQLFetchConfig<TData, TResponse = any> {\n /** GraphQL endpoint URL */\n endpoint: string;\n\n /** GraphQL queries */\n queries: {\n /** Query for fetching entries */\n entries: string;\n /** Optional separate query for count (if not in entries query) */\n count?: string;\n };\n\n /** Transform GraphQL response to entries array */\n transformResponse: (data: TResponse) => TData[] | null;\n\n /** Transform GraphQL response to count */\n transformCount: (data: TResponse) => number;\n\n /** Optional: Compose additional variables */\n composeVariables?: (baseVariables: any) => any;\n}\n\n/**\n * Create a GraphQL fetch strategy\n *\n * This factory handles the common patterns for GraphQL APIs:\n * - Fetching with variables\n * - Response transformation\n * - Count vs entries queries\n * - Error handling\n *\n * @template TData - The type of entry data\n * @template TResponse - The type of raw GraphQL response\n * @template TVariables - The type of GraphQL variables\n *\n * @param config - Configuration for the strategy\n * @returns Fetch strategy for use with createBaseFeed\n *\n * @example\n * ```typescript\n * const eventsFetchStrategy = createGraphQLFetchStrategy({\n * endpoint: 'https://calendar.umd.edu/graphql',\n * queries: {\n * entries: EVENTS_QUERY,\n * count: EVENTS_COUNT_QUERY,\n * },\n * transformResponse: (data) => data?.data?.entries?.events || null,\n * transformCount: (data) => data?.data?.count?.events?.length || 0,\n * });\n * ```\n */\nexport function createGraphQLFetchStrategy<\n TData,\n TResponse = any,\n TVariables = any,\n>(\n config: GraphQLFetchConfig<TData, TResponse>,\n): FetchStrategy<TData, TVariables> {\n const {\n endpoint,\n queries,\n transformResponse,\n transformCount,\n composeVariables,\n } = config;\n\n return {\n /**\n * Fetch the total count of entries\n */\n fetchCount: async (variables: TVariables): Promise<number | null> => {\n try {\n // Use count query if provided, otherwise use entries query\n const query = queries.count || queries.entries;\n const response = await fetchGraphQL({\n url: endpoint,\n query,\n token: (variables as any).token,\n variables: variables as GraphQLVariables,\n });\n\n // Check for errors\n if (!response || !response.data || response.message) {\n if (response?.message) {\n console.error(`GraphQL Error: ${response.message}`);\n }\n return null;\n }\n\n return transformCount(response);\n } catch (error) {\n console.error('Fetch count error:', error);\n return null;\n }\n },\n\n /**\n * Fetch the actual entries\n */\n fetchEntries: async (variables: TVariables): Promise<TData[] | null> => {\n try {\n const response = await fetchGraphQL({\n url: endpoint,\n query: queries.entries,\n token: (variables as any).token,\n variables: variables as GraphQLVariables,\n });\n\n // Check for errors\n if (!response || !response.data || response.message) {\n if (response?.message) {\n console.error(`GraphQL Error: ${response.message}`);\n }\n return null;\n }\n\n return transformResponse(response);\n } catch (error) {\n console.error('Fetch entries error:', error);\n return null;\n }\n },\n\n /**\n * Compose API variables from feed props\n */\n composeApiVariables: (props: any): TVariables => {\n const {\n token,\n categories,\n numberOfColumnsToShow = 1,\n numberOfRowsToStart,\n getOffset,\n entriesToRemove,\n ids,\n isMediaTrained,\n } = props;\n\n // Base variables - pass all props through\n const baseVariables: any = {\n token,\n limit: numberOfColumnsToShow * numberOfRowsToStart,\n offset: getOffset ? getOffset() : 0,\n };\n\n // Pass categories through (let composeVariables decide how to map it)\n if (categories) {\n baseVariables.categories = categories;\n }\n\n // Pass entriesToRemove through\n if (entriesToRemove) {\n baseVariables.entriesToRemove = entriesToRemove;\n }\n\n // Pass id through\n if (ids) {\n baseVariables.ids = ids;\n }\n\n // Pass isMediaTrained through (can be true, false, or null)\n if (isMediaTrained !== undefined) {\n baseVariables.isMediaTrained = isMediaTrained;\n }\n\n // Pass expertId through (for inTheNews strategy)\n if (props.expertId) {\n baseVariables.expertId = props.expertId;\n }\n\n // Allow custom variable composition\n if (composeVariables) {\n return composeVariables(baseVariables);\n }\n\n // Default mapping (for strategies without custom composeVariables)\n const defaultVariables: any = { ...baseVariables };\n\n // Default: map categories to 'related' if not customized\n if (categories) {\n defaultVariables.related = categories;\n delete defaultVariables.categories;\n }\n\n return defaultVariables as TVariables;\n },\n };\n}\n"],"names":[],"mappings":";AAqEO,SAAS,2BAKd,QACkC;AAClC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,IACE;AAEJ,SAAO;AAAA;AAAA;AAAA;AAAA,IAIL,YAAY,OAAO,cAAkD;AACnE,UAAI;AAEF,cAAM,QAAQ,QAAQ,SAAS,QAAQ;AACvC,cAAM,WAAW,MAAM,aAAa;AAAA,UAClC,KAAK;AAAA,UACL;AAAA,UACA,OAAQ,UAAkB;AAAA,UAC1B;AAAA,QAAA,CACD;AAGD,YAAI,CAAC,YAAY,CAAC,SAAS,QAAQ,SAAS,SAAS;AACnD,cAAI,UAAU,SAAS;AACrB,oBAAQ,MAAM,kBAAkB,SAAS,OAAO,EAAE;AAAA,UACpD;AACA,iBAAO;AAAA,QACT;AAEA,eAAO,eAAe,QAAQ;AAAA,MAChC,SAAS,OAAO;AACd,gBAAQ,MAAM,sBAAsB,KAAK;AACzC,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,cAAc,OAAO,cAAmD;AACtE,UAAI;AACF,cAAM,WAAW,MAAM,aAAa;AAAA,UAClC,KAAK;AAAA,UACL,OAAO,QAAQ;AAAA,UACf,OAAQ,UAAkB;AAAA,UAC1B;AAAA,QAAA,CACD;AAGD,YAAI,CAAC,YAAY,CAAC,SAAS,QAAQ,SAAS,SAAS;AACnD,cAAI,UAAU,SAAS;AACrB,oBAAQ,MAAM,kBAAkB,SAAS,OAAO,EAAE;AAAA,UACpD;AACA,iBAAO;AAAA,QACT;AAEA,eAAO,kBAAkB,QAAQ;AAAA,MACnC,SAAS,OAAO;AACd,gBAAQ,MAAM,wBAAwB,KAAK;AAC3C,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,qBAAqB,CAAC,UAA2B;AAC/C,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA,wBAAwB;AAAA,QACxB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA,IACE;AAGJ,YAAM,gBAAqB;AAAA,QACzB;AAAA,QACA,OAAO,wBAAwB;AAAA,QAC/B,QAAQ,YAAY,cAAc;AAAA,MAAA;AAIpC,UAAI,YAAY;AACd,sBAAc,aAAa;AAAA,MAC7B;AAGA,UAAI,iBAAiB;AACnB,sBAAc,kBAAkB;AAAA,MAClC;AAGA,UAAI,KAAK;AACP,sBAAc,MAAM;AAAA,MACtB;AAGA,UAAI,mBAAmB,QAAW;AAChC,sBAAc,iBAAiB;AAAA,MACjC;AAGA,UAAI,MAAM,UAAU;AAClB,sBAAc,WAAW,MAAM;AAAA,MACjC;AAGA,UAAI,kBAAkB;AACpB,eAAO,iBAAiB,aAAa;AAAA,MACvC;AAGA,YAAM,mBAAwB,EAAE,GAAG,cAAA;AAGnC,UAAI,YAAY;AACd,yBAAiB,UAAU;AAC3B,eAAO,iBAAiB;AAAA,MAC1B;AAEA,aAAO;AAAA,IACT;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"graphql.js","sources":["../../../source/strategies/fetch/graphql.ts"],"sourcesContent":["/**\n * GraphQL Fetch Strategy Factory\n *\n * Generic factory for creating GraphQL-based fetch strategies.\n * Handles common GraphQL patterns: queries, variables, transformations.\n *\n * @module strategies/fetch/graphql\n */\n\nimport {\n fetchGraphQL,\n GraphQLVariables,\n} from '@universityofmaryland/web-utilities-library/network';\nimport { FetchStrategy } from '../../factory/core/types';\n\n/**\n * Configuration for creating a GraphQL fetch strategy\n */\nexport interface GraphQLFetchConfig<TData, TResponse = any> {\n /** GraphQL endpoint URL */\n endpoint: string;\n\n /** GraphQL queries */\n queries: {\n /** Query for fetching entries */\n entries: string;\n /** Optional separate query for count (if not in entries query) */\n count?: string;\n };\n\n /** Transform GraphQL response to entries array */\n transformResponse: (data: TResponse) => TData[] | null;\n\n /** Transform GraphQL response to count */\n transformCount: (data: TResponse) => number;\n\n /** Optional: Compose additional variables */\n composeVariables?: (baseVariables: any) => any;\n}\n\n/**\n * Create a GraphQL fetch strategy\n *\n * This factory handles the common patterns for GraphQL APIs:\n * - Fetching with variables\n * - Response transformation\n * - Count vs entries queries\n * - Error handling\n *\n * @template TData - The type of entry data\n * @template TResponse - The type of raw GraphQL response\n * @template TVariables - The type of GraphQL variables\n *\n * @param config - Configuration for the strategy\n * @returns Fetch strategy for use with createBaseFeed\n *\n * @example\n * ```typescript\n * const eventsFetchStrategy = createGraphQLFetchStrategy({\n * endpoint: 'https://calendar.umd.edu/graphql',\n * queries: {\n * entries: EVENTS_QUERY,\n * count: EVENTS_COUNT_QUERY,\n * },\n * transformResponse: (data) => data?.data?.entries?.events || null,\n * transformCount: (data) => data?.data?.count?.events?.length || 0,\n * });\n * ```\n */\nexport function createGraphQLFetchStrategy<\n TData,\n TResponse = any,\n TVariables = any,\n>(\n config: GraphQLFetchConfig<TData, TResponse>,\n): FetchStrategy<TData, TVariables> {\n const {\n endpoint,\n queries,\n transformResponse,\n transformCount,\n composeVariables,\n } = config;\n\n return {\n /**\n * Fetch the total count of entries\n */\n fetchCount: async (variables: TVariables): Promise<number | null> => {\n try {\n // Use count query if provided, otherwise use entries query\n const query = queries.count || queries.entries;\n const response = await fetchGraphQL({\n url: endpoint,\n query,\n token: (variables as any).token,\n variables: variables as GraphQLVariables,\n });\n\n // Check for errors\n if (!response || !response.data || response.message) {\n if (response?.message) {\n console.error(`GraphQL Error: ${response.message}`);\n }\n return null;\n }\n\n return transformCount(response);\n } catch (error) {\n console.error('Fetch count error:', error);\n return null;\n }\n },\n\n /**\n * Fetch the actual entries\n */\n fetchEntries: async (variables: TVariables): Promise<TData[] | null> => {\n try {\n const response = await fetchGraphQL({\n url: endpoint,\n query: queries.entries,\n token: (variables as any).token,\n variables: variables as GraphQLVariables,\n });\n\n // Check for errors\n if (!response || !response.data || response.message) {\n if (response?.message) {\n console.error(`GraphQL Error: ${response.message}`);\n }\n return null;\n }\n\n return transformResponse(response);\n } catch (error) {\n console.error('Fetch entries error:', error);\n return null;\n }\n },\n\n /**\n * Compose API variables from feed props\n */\n composeApiVariables: (props: any): TVariables => {\n const {\n token,\n categories,\n numberOfColumnsToShow = 1,\n numberOfRowsToStart,\n getOffset,\n entriesToRemove,\n ids,\n isLazyLoad,\n ...extraProps\n } = props;\n\n // Base variables shared by all strategies\n const baseVariables: any = {\n token,\n limit: numberOfColumnsToShow * numberOfRowsToStart,\n offset: getOffset ? getOffset() : 0,\n };\n\n if (categories) {\n baseVariables.categories = categories;\n }\n\n if (entriesToRemove) {\n baseVariables.entriesToRemove = entriesToRemove;\n }\n\n if (ids) {\n baseVariables.ids = ids;\n }\n\n // Forward strategy-specific props (e.g. isMediaTrained, isUnion, expertId)\n Object.assign(baseVariables, extraProps);\n\n // Allow custom variable composition\n if (composeVariables) {\n return composeVariables(baseVariables);\n }\n\n // Default mapping (for strategies without custom composeVariables)\n const defaultVariables: any = { ...baseVariables };\n\n // Default: map categories to 'related' if not customized\n if (categories) {\n defaultVariables.related = categories;\n delete defaultVariables.categories;\n }\n\n return defaultVariables as TVariables;\n },\n };\n}\n"],"names":[],"mappings":";AAqEO,SAAS,2BAKd,QACkC;AAClC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,IACE;AAEJ,SAAO;AAAA;AAAA;AAAA;AAAA,IAIL,YAAY,OAAO,cAAkD;AACnE,UAAI;AAEF,cAAM,QAAQ,QAAQ,SAAS,QAAQ;AACvC,cAAM,WAAW,MAAM,aAAa;AAAA,UAClC,KAAK;AAAA,UACL;AAAA,UACA,OAAQ,UAAkB;AAAA,UAC1B;AAAA,QAAA,CACD;AAGD,YAAI,CAAC,YAAY,CAAC,SAAS,QAAQ,SAAS,SAAS;AACnD,cAAI,UAAU,SAAS;AACrB,oBAAQ,MAAM,kBAAkB,SAAS,OAAO,EAAE;AAAA,UACpD;AACA,iBAAO;AAAA,QACT;AAEA,eAAO,eAAe,QAAQ;AAAA,MAChC,SAAS,OAAO;AACd,gBAAQ,MAAM,sBAAsB,KAAK;AACzC,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,cAAc,OAAO,cAAmD;AACtE,UAAI;AACF,cAAM,WAAW,MAAM,aAAa;AAAA,UAClC,KAAK;AAAA,UACL,OAAO,QAAQ;AAAA,UACf,OAAQ,UAAkB;AAAA,UAC1B;AAAA,QAAA,CACD;AAGD,YAAI,CAAC,YAAY,CAAC,SAAS,QAAQ,SAAS,SAAS;AACnD,cAAI,UAAU,SAAS;AACrB,oBAAQ,MAAM,kBAAkB,SAAS,OAAO,EAAE;AAAA,UACpD;AACA,iBAAO;AAAA,QACT;AAEA,eAAO,kBAAkB,QAAQ;AAAA,MACnC,SAAS,OAAO;AACd,gBAAQ,MAAM,wBAAwB,KAAK;AAC3C,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,qBAAqB,CAAC,UAA2B;AAC/C,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA,wBAAwB;AAAA,QACxB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG;AAAA,MAAA,IACD;AAGJ,YAAM,gBAAqB;AAAA,QACzB;AAAA,QACA,OAAO,wBAAwB;AAAA,QAC/B,QAAQ,YAAY,cAAc;AAAA,MAAA;AAGpC,UAAI,YAAY;AACd,sBAAc,aAAa;AAAA,MAC7B;AAEA,UAAI,iBAAiB;AACnB,sBAAc,kBAAkB;AAAA,MAClC;AAEA,UAAI,KAAK;AACP,sBAAc,MAAM;AAAA,MACtB;AAGA,aAAO,OAAO,eAAe,UAAU;AAGvC,UAAI,kBAAkB;AACpB,eAAO,iBAAiB,aAAa;AAAA,MACvC;AAGA,YAAM,mBAAwB,EAAE,GAAG,cAAA;AAGnC,UAAI,YAAY;AACd,yBAAiB,UAAU;AAC3B,eAAO,iBAAiB;AAAA,MAC1B;AAEA,aAAO;AAAA,IACT;AAAA,EAAA;AAEJ;"}
@@ -10,6 +10,7 @@ export interface ExpertJob {
10
10
  title: string;
11
11
  url: string;
12
12
  roomNumber?: string;
13
+ collegeSchools?: CampusUnit[] | null;
13
14
  campusUnits?: CampusUnit[] | null;
14
15
  }
15
16
  export interface ExpertOrganization {
@@ -1 +1 @@
1
- {"version":3,"file":"experts.d.ts","sourceRoot":"","sources":["../../../source/types/data/experts.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAO3C,MAAM,WAAW,UAAU;IAEzB,KAAK,EAAE,MAAM,CAAC;IAEd,IAAI,CAAC,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;KACb,GAAG,IAAI,CAAC;CACV;AAOD,MAAM,WAAW,SAAS;IAExB,EAAE,EAAE,MAAM,CAAC;IAEX,KAAK,EAAE,MAAM,CAAC;IAEd,GAAG,EAAE,MAAM,CAAC;IAEZ,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,WAAW,CAAC,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;CACnC;AAOD,MAAM,WAAW,kBAAkB;IAEjC,EAAE,EAAE,MAAM,CAAC;IAEX,KAAK,EAAE,MAAM,CAAC;IAEd,GAAG,EAAE,MAAM,CAAC;IAEZ,IAAI,EAAE,SAAS,EAAE,CAAC;CACnB;AAOD,MAAM,WAAW,aAAa;IAE5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAExB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAOD,MAAM,WAAW,YAAY;IAE3B,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAE5B,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC/B;AAOD,MAAM,WAAW,cAAc;IAE7B,EAAE,EAAE,MAAM,CAAC;IAEX,KAAK,EAAE,MAAM,CAAC;IAEd,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAQD,MAAM,WAAW,WAAY,SAAQ,SAAS;IAE5C,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvB,SAAS,EAAE,MAAM,CAAC;IAElB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE3B,QAAQ,EAAE,MAAM,CAAC;IAEjB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzB,QAAQ,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IAE1B,OAAO,CAAC,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;KACd,GAAG,IAAI,CAAC;IAET,GAAG,CAAC,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;KACd,GAAG,IAAI,CAAC;IAET,aAAa,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAErC,gBAAgB,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAE3C,MAAM,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAEjC,WAAW,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAEtC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzB,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC/B"}
1
+ {"version":3,"file":"experts.d.ts","sourceRoot":"","sources":["../../../source/types/data/experts.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAO3C,MAAM,WAAW,UAAU;IAEzB,KAAK,EAAE,MAAM,CAAC;IAEd,IAAI,CAAC,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;KACb,GAAG,IAAI,CAAC;CACV;AAOD,MAAM,WAAW,SAAS;IAExB,EAAE,EAAE,MAAM,CAAC;IAEX,KAAK,EAAE,MAAM,CAAC;IAEd,GAAG,EAAE,MAAM,CAAC;IAEZ,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,cAAc,CAAC,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAErC,WAAW,CAAC,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;CACnC;AAOD,MAAM,WAAW,kBAAkB;IAEjC,EAAE,EAAE,MAAM,CAAC;IAEX,KAAK,EAAE,MAAM,CAAC;IAEd,GAAG,EAAE,MAAM,CAAC;IAEZ,IAAI,EAAE,SAAS,EAAE,CAAC;CACnB;AAOD,MAAM,WAAW,aAAa;IAE5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAExB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAOD,MAAM,WAAW,YAAY;IAE3B,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAE5B,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC/B;AAOD,MAAM,WAAW,cAAc;IAE7B,EAAE,EAAE,MAAM,CAAC;IAEX,KAAK,EAAE,MAAM,CAAC;IAEd,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAQD,MAAM,WAAW,WAAY,SAAQ,SAAS;IAE5C,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvB,SAAS,EAAE,MAAM,CAAC;IAElB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE3B,QAAQ,EAAE,MAAM,CAAC;IAEjB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzB,QAAQ,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IAE1B,OAAO,CAAC,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;KACd,GAAG,IAAI,CAAC;IAET,GAAG,CAAC,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;KACd,GAAG,IAAI,CAAC;IAET,aAAa,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAErC,gBAAgB,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAE3C,MAAM,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAEjC,WAAW,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAEtC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzB,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC/B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@universityofmaryland/web-feeds-library",
3
- "version": "1.3.5",
3
+ "version": "1.3.6",
4
4
  "description": "UMD Feed Elements",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -71,6 +71,8 @@
71
71
  "@universityofmaryland/web-utilities-library": "^1.0.0"
72
72
  },
73
73
  "devDependencies": {
74
+ "typescript": "^5.8.2",
75
+ "vite": "^7.1.4",
74
76
  "vite-plugin-checker": "^0.10.3",
75
77
  "vite-plugin-dts": "^4.5.4"
76
78
  }