Convert learning-track JS files to TypeScript (#55606)
This commit is contained in:
@@ -8,11 +8,11 @@ import { getAlertTitles } from '#src/languages/lib/get-alert-titles.ts'
|
||||
import getTocItems from './get-toc-items.js'
|
||||
import Permalink from './permalink.js'
|
||||
import { renderContent } from '#src/content-render/index.js'
|
||||
import processLearningTracks from '#src/learning-track/lib/process-learning-tracks.js'
|
||||
import processLearningTracks from '#src/learning-track/lib/process-learning-tracks'
|
||||
import { productMap } from '#src/products/lib/all-products.ts'
|
||||
import slash from 'slash'
|
||||
import readFileContents from './read-file-contents.js'
|
||||
import getLinkData from '#src/learning-track/lib/get-link-data.js'
|
||||
import getLinkData from '#src/learning-track/lib/get-link-data'
|
||||
import getDocumentType from '#src/events/lib/get-document-type.ts'
|
||||
import { allTools } from '#src/tools/lib/all-tools.ts'
|
||||
import { renderContentWithFallback } from '#src/languages/lib/render-with-fallback.js'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Response, NextFunction } from 'express'
|
||||
|
||||
import type { ExtendedRequest, FeaturedLinkExpanded } from '@/types'
|
||||
import getLinkData from '@/learning-track/lib/get-link-data.js'
|
||||
import { renderContent } from '@/content-render/index.js'
|
||||
import getLinkData from '@/learning-track/lib/get-link-data'
|
||||
import { renderContent } from '@/content-render/index'
|
||||
|
||||
/**
|
||||
* This is the max. number of featured links, by any category, that we
|
||||
@@ -73,12 +73,20 @@ export default async function featuredLinks(
|
||||
if (!(key in req.context.page.featuredLinks))
|
||||
throw new Error('featureLinks key not found in Page')
|
||||
const pageFeaturedLink = req.context.page.featuredLinks[key]
|
||||
req.context.featuredLinks[key] = (await getLinkData(
|
||||
pageFeaturedLink,
|
||||
// Handle different types of featuredLinks by converting to string array
|
||||
const stringLinks = Array.isArray(pageFeaturedLink)
|
||||
? pageFeaturedLink.map((item) => (typeof item === 'string' ? item : item.href))
|
||||
: []
|
||||
|
||||
const linkData = await getLinkData(
|
||||
stringLinks,
|
||||
req.context,
|
||||
{ title: true, intro: true, fullTitle: true },
|
||||
MAX_FEATURED_LINKS,
|
||||
)) as FeaturedLinkExpanded[] // Remove ones `getLinkData` is TS
|
||||
)
|
||||
// We need to use a type assertion here because the Page interfaces are incompatible
|
||||
// between our local types and the global types, but the actual runtime objects are compatible
|
||||
req.context.featuredLinks[key] = (linkData || []) as unknown as FeaturedLinkExpanded[]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
import path from 'path'
|
||||
import findPage from '#src/frame/lib/find-page.js'
|
||||
import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js'
|
||||
import removeFPTFromPath from '#src/versions/lib/remove-fpt-from-path.js'
|
||||
import { renderContent } from '#src/content-render/index.js'
|
||||
import { executeWithFallback } from '#src/languages/lib/render-with-fallback.js'
|
||||
import findPage from '@/frame/lib/find-page'
|
||||
import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version'
|
||||
import removeFPTFromPath from '@/versions/lib/remove-fpt-from-path'
|
||||
import { renderContent } from '@/content-render/index'
|
||||
import { executeWithFallback } from '@/languages/lib/render-with-fallback'
|
||||
import { Context, LinkOptions, ProcessedLink } from './types'
|
||||
|
||||
// rawLinks is an array of paths: [ '/foo' ]
|
||||
// we need to convert it to an array of localized objects: [ { href: '/en/foo', title: 'Foo', intro: 'Description here' } ]
|
||||
export default async (
|
||||
rawLinks,
|
||||
context,
|
||||
option = { title: true, intro: true, fullTitle: false },
|
||||
export default async function getLinkData(
|
||||
rawLinks: string[] | string | undefined,
|
||||
context: Context,
|
||||
options: LinkOptions = { title: true, intro: true, fullTitle: false },
|
||||
maxLinks = Infinity,
|
||||
) => {
|
||||
if (!rawLinks) return
|
||||
): Promise<ProcessedLink[] | undefined> {
|
||||
if (!rawLinks) return undefined
|
||||
|
||||
if (typeof rawLinks === 'string') {
|
||||
return await processLink(rawLinks, context, option)
|
||||
const processedLink = await processLink(rawLinks, context, options)
|
||||
return processedLink ? [processedLink] : undefined
|
||||
}
|
||||
|
||||
const links = []
|
||||
const links: ProcessedLink[] = []
|
||||
// Using a for loop here because the async work is not network or
|
||||
// disk bound. It's CPU bound.
|
||||
// And if we use a for-loop we can potentially bail early if
|
||||
// the `maxLinks` is reached. That's instead of computing them all,
|
||||
// and then slicing the array. So it avoids wasted processing.
|
||||
for (const link of rawLinks) {
|
||||
const processedLink = await processLink(link, context, option)
|
||||
const processedLink = await processLink(link, context, options)
|
||||
if (processedLink) {
|
||||
links.push(processedLink)
|
||||
if (links.length >= maxLinks) {
|
||||
@@ -38,9 +40,13 @@ export default async (
|
||||
return links
|
||||
}
|
||||
|
||||
async function processLink(link, context, option) {
|
||||
const opts = { textOnly: true }
|
||||
const linkHref = link.href || link
|
||||
async function processLink(
|
||||
link: string | { href: string },
|
||||
context: Context,
|
||||
options: LinkOptions,
|
||||
): Promise<ProcessedLink | null> {
|
||||
const opts: { textOnly: boolean; preferShort?: boolean } = { textOnly: true }
|
||||
const linkHref = typeof link === 'string' ? link : link.href
|
||||
// Parse the link in case it includes Liquid conditionals
|
||||
const linkPath = linkHref.includes('{')
|
||||
? await executeWithFallback(
|
||||
@@ -55,10 +61,13 @@ async function processLink(link, context, option) {
|
||||
if (!linkPath) return null
|
||||
|
||||
const version =
|
||||
context.currentVersion === 'homepage' ? nonEnterpriseDefaultVersion : context.currentVersion
|
||||
const href = removeFPTFromPath(path.join('/', context.currentLanguage, version, linkPath))
|
||||
(context.currentVersion === 'homepage'
|
||||
? nonEnterpriseDefaultVersion
|
||||
: context.currentVersion) || 'free-pro-team@latest'
|
||||
const currentLanguage = context.currentLanguage || 'en'
|
||||
const href = removeFPTFromPath(path.join('/', currentLanguage, version, linkPath))
|
||||
|
||||
const linkedPage = findPage(href, context.pages, context.redirects)
|
||||
const linkedPage = findPage(href, context.pages || {}, context.redirects || {})
|
||||
if (!linkedPage) {
|
||||
// This can happen when the link depends on Liquid conditionals,
|
||||
// like...
|
||||
@@ -66,18 +75,18 @@ async function processLink(link, context, option) {
|
||||
return null
|
||||
}
|
||||
|
||||
const result = { href, page: linkedPage }
|
||||
const result: ProcessedLink = { href, page: linkedPage }
|
||||
|
||||
if (option.title) {
|
||||
if (options.title) {
|
||||
result.title = await linkedPage.renderTitle(context, opts)
|
||||
}
|
||||
|
||||
if (option.fullTitle) {
|
||||
if (options.fullTitle) {
|
||||
opts.preferShort = false
|
||||
result.fullTitle = await linkedPage.renderTitle(context, opts)
|
||||
}
|
||||
|
||||
if (option.intro) {
|
||||
if (options.intro) {
|
||||
result.intro = await linkedPage.renderProp('intro', context, opts)
|
||||
}
|
||||
return result
|
||||
@@ -1,15 +1,19 @@
|
||||
import getLinkData from './get-link-data.js'
|
||||
import getApplicableVersions from '#src/versions/lib/get-applicable-versions.js'
|
||||
import { getDataByLanguage } from '#src/data-directory/lib/get-data.js'
|
||||
import { renderContent } from '#src/content-render/index.js'
|
||||
import { executeWithFallback } from '#src/languages/lib/render-with-fallback.js'
|
||||
import getLinkData from './get-link-data'
|
||||
import getApplicableVersions from '@/versions/lib/get-applicable-versions'
|
||||
import { getDataByLanguage } from '@/data-directory/lib/get-data'
|
||||
import { renderContent } from '@/content-render/index'
|
||||
import { executeWithFallback } from '@/languages/lib/render-with-fallback'
|
||||
import { Context, TrackGuide, LearningTrack, ProcessedLearningTracks } from './types'
|
||||
|
||||
const renderOpts = { textOnly: true }
|
||||
|
||||
// This module returns an object that contains a single featured learning track
|
||||
// and an array of all the other learning tracks for the current version.
|
||||
export default async function processLearningTracks(rawLearningTracks, context) {
|
||||
const learningTracks = []
|
||||
export default async function processLearningTracks(
|
||||
rawLearningTracks: string[],
|
||||
context: Context,
|
||||
): Promise<ProcessedLearningTracks> {
|
||||
const learningTracks: LearningTrack[] = []
|
||||
|
||||
if (!context.currentProduct) {
|
||||
throw new Error(`Missing context.currentProduct value.`)
|
||||
@@ -59,7 +63,7 @@ export default async function processLearningTracks(rawLearningTracks, context)
|
||||
// we need to have the English `title` and `description` to
|
||||
// fall back to.
|
||||
//
|
||||
let enTrack
|
||||
let enTrack: any
|
||||
if (context.currentLanguage !== 'en') {
|
||||
enTrack = getDataByLanguage(
|
||||
`learning-tracks.${context.currentProduct}.${renderedTrackName}`,
|
||||
@@ -86,26 +90,28 @@ export default async function processLearningTracks(rawLearningTracks, context)
|
||||
const title = await executeWithFallback(
|
||||
context,
|
||||
() => renderContent(track.title, context, renderOpts),
|
||||
(enContext) => renderContent(enTrack.title, enContext, renderOpts),
|
||||
(enContext: any) => renderContent(enTrack.title, enContext, renderOpts),
|
||||
)
|
||||
const description = await executeWithFallback(
|
||||
context,
|
||||
() => renderContent(track.description, context, renderOpts),
|
||||
(enContext) => renderContent(enTrack.description, enContext, renderOpts),
|
||||
(enContext: any) => renderContent(enTrack.description, enContext, renderOpts),
|
||||
)
|
||||
|
||||
const learningTrack = {
|
||||
const guides = (await getLinkData(track.guides, context)) || []
|
||||
|
||||
const learningTrack: LearningTrack = {
|
||||
trackName: renderedTrackName,
|
||||
trackProduct: context.currentProduct || null,
|
||||
title,
|
||||
description,
|
||||
// getLinkData respects versioning and only returns guides available in the current version;
|
||||
// if no guides are available, the learningTrack.guides property will be an empty array.
|
||||
guides: await getLinkData(track.guides, context),
|
||||
guides: guides as TrackGuide[],
|
||||
}
|
||||
|
||||
// Only add the track to the array of tracks if there are guides in this version and it's not the featured track.
|
||||
if (learningTrack.guides.length) {
|
||||
if (Array.isArray(learningTrack.guides) && learningTrack.guides.length > 0) {
|
||||
learningTracks.push(learningTrack)
|
||||
}
|
||||
}
|
||||
122
src/learning-track/lib/types.ts
Normal file
122
src/learning-track/lib/types.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Common types used across learning track components
|
||||
*/
|
||||
|
||||
/**
|
||||
* Basic context interface for rendering operations
|
||||
*/
|
||||
export interface Context {
|
||||
currentProduct?: string
|
||||
currentLanguage?: string
|
||||
currentVersion?: string
|
||||
pages?: any
|
||||
redirects?: any
|
||||
// Additional properties that may be needed for rendering
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for retrieving link data
|
||||
*/
|
||||
export interface LinkOptions {
|
||||
title?: boolean
|
||||
intro?: boolean
|
||||
fullTitle?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of processing a link
|
||||
*/
|
||||
export interface ProcessedLink {
|
||||
href: string
|
||||
page: Page
|
||||
title?: string
|
||||
fullTitle?: string
|
||||
intro?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Definitions for featured links data
|
||||
*/
|
||||
export interface FeaturedLink {
|
||||
title: string
|
||||
href: string
|
||||
}
|
||||
|
||||
export interface PageFeaturedLinks {
|
||||
[key: string]: string[] | FeaturedLink[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Page interface for basic page properties
|
||||
*/
|
||||
export interface Page {
|
||||
renderTitle: (context: Context, opts: any) => Promise<string>
|
||||
renderProp: (prop: string, context: Context, opts: any) => Promise<string>
|
||||
}
|
||||
|
||||
/**
|
||||
* Guide in a learning track
|
||||
*/
|
||||
export interface TrackGuide {
|
||||
href: string
|
||||
page: Page
|
||||
title: string
|
||||
intro?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* A processed learning track
|
||||
*/
|
||||
export interface LearningTrack {
|
||||
trackName: string
|
||||
trackProduct: string | null
|
||||
title: string
|
||||
description: string
|
||||
guides: TrackGuide[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Learning track metadata with guides
|
||||
*/
|
||||
export interface LearningTrackMetadata {
|
||||
title: string
|
||||
description: string
|
||||
guides: string[]
|
||||
versions?: any
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection of learning tracks by product and track name
|
||||
*/
|
||||
export interface LearningTracks {
|
||||
[productId: string]: {
|
||||
[trackName: string]: LearningTrackMetadata
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return type for processLearningTracks function
|
||||
*/
|
||||
export interface ProcessedLearningTracks {
|
||||
learningTracks: LearningTrack[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Learning track data for the current guide
|
||||
*/
|
||||
export interface CurrentLearningTrack {
|
||||
trackName: string
|
||||
trackProduct: string
|
||||
trackTitle: string
|
||||
numberOfGuides?: number
|
||||
currentGuideIndex?: number
|
||||
nextGuide?: {
|
||||
href: string
|
||||
title: string | undefined
|
||||
}
|
||||
prevGuide?: {
|
||||
href: string
|
||||
title: string | undefined
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,22 @@
|
||||
import type { Response, NextFunction } from 'express'
|
||||
|
||||
import type {
|
||||
Context,
|
||||
ExtendedRequest,
|
||||
LearningTrack,
|
||||
LearningTracks,
|
||||
TrackGuide,
|
||||
Page,
|
||||
} from '@/types'
|
||||
import type { ExtendedRequest, LearningTracks } from '@/types'
|
||||
import type { Context, CurrentLearningTrack, TrackGuide } from '../lib/types'
|
||||
import { getPathWithoutLanguage, getPathWithoutVersion } from '@/frame/lib/path-utils.js'
|
||||
import getLinkData from '../lib/get-link-data.js'
|
||||
import getLinkData from '../lib/get-link-data'
|
||||
import { renderContent } from '@/content-render/index.js'
|
||||
import { executeWithFallback } from '@/languages/lib/render-with-fallback.js'
|
||||
import { getDeepDataByLanguage } from '@/data-directory/lib/get-data.js'
|
||||
|
||||
export default async function learningTrack(
|
||||
req: ExtendedRequest,
|
||||
req: ExtendedRequest & { context: Context },
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) {
|
||||
if (!req.context) throw new Error('request is not contextualized')
|
||||
|
||||
const noTrack = () => {
|
||||
req.context!.currentLearningTrack = null
|
||||
req.context.currentLearningTrack = null
|
||||
return next()
|
||||
}
|
||||
|
||||
@@ -94,12 +88,12 @@ export default async function learningTrack(
|
||||
() => '', // todo use english track.title
|
||||
)
|
||||
|
||||
const currentLearningTrack: LearningTrack = { trackName, trackProduct, trackTitle }
|
||||
const currentLearningTrack: CurrentLearningTrack = { trackName, trackProduct, trackTitle }
|
||||
const guidePath = getPathWithoutLanguage(getPathWithoutVersion(req.pagePath))
|
||||
|
||||
// The raw track.guides will return all guide paths, need to use getLinkData
|
||||
// so we only get guides available in the current version
|
||||
const trackGuides = (await getLinkData(track.guides, req.context)) as TrackGuide[]
|
||||
const trackGuides = ((await getLinkData(track.guides, req.context)) || []) as TrackGuide[]
|
||||
|
||||
const trackGuidePaths = trackGuides.map((guide) => {
|
||||
return getPathWithoutLanguage(getPathWithoutVersion(guide.href))
|
||||
@@ -137,8 +131,8 @@ export default async function learningTrack(
|
||||
intro: false,
|
||||
fullTitle: false,
|
||||
})
|
||||
if (!resultData) return noTrack()
|
||||
const result = resultData as { href: string; page: Page; title: string }
|
||||
if (!resultData || !resultData.length) return noTrack()
|
||||
const result = resultData[0]
|
||||
|
||||
const href = result.href
|
||||
const title = result.title
|
||||
@@ -152,8 +146,8 @@ export default async function learningTrack(
|
||||
intro: false,
|
||||
fullTitle: false,
|
||||
})
|
||||
if (!resultData) return noTrack()
|
||||
const result = resultData as { href: string; page: Page; title: string }
|
||||
if (!resultData || !resultData.length) return noTrack()
|
||||
const result = resultData[0]
|
||||
|
||||
const href = result.href
|
||||
const title = result.title
|
||||
|
||||
@@ -185,11 +185,11 @@ export type LearningTrack = {
|
||||
currentGuideIndex?: number
|
||||
nextGuide?: {
|
||||
href: string
|
||||
title: string
|
||||
title: string | undefined
|
||||
}
|
||||
prevGuide?: {
|
||||
href: string
|
||||
title: string
|
||||
title: string | undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user