AbdulElahGwaith's picture
Upload folder using huggingface_hub
88df9e4 verified
import type { Response, NextFunction } from 'express'
import type { ExtendedRequest, LearningTracks } from '@/types'
import type { Context, CurrentLearningTrack, TrackGuide } from '../lib/types'
import { getPathWithoutLanguage, getPathWithoutVersion } from '@/frame/lib/path-utils'
import getLinkData from '../lib/get-link-data'
import { renderContent } from '@/content-render/index'
import { executeWithFallback } from '@/languages/lib/render-with-fallback'
import { getDeepDataByLanguage } from '@/data-directory/lib/get-data'
export default async function learningTrack(
req: ExtendedRequest & { context: Context },
res: Response,
next: NextFunction,
) {
if (!req.context) throw new Error('request is not contextualized')
const noTrack = () => {
req.context.currentLearningTrack = null
return next()
}
if (!req.context.page) return next()
if (!req.query.learn) return noTrack()
if (Array.isArray(req.query.learn)) return noTrack()
const trackName = req.query.learn as string
let trackProduct = req.context.currentProduct as string
const allLearningTracks = getDeepDataByLanguage(
'learning-tracks',
req.language!,
) as LearningTracks
if (req.language !== 'en') {
// Don't trust the `.guides` from the translation. It too often has
// broken Liquid (e.g. `{% ifversion fpt 또는 ghec 또는 ghes %}`)
const allEnglishLearningTracks = getDeepDataByLanguage(
'learning-tracks',
'en',
) as LearningTracks
for (const [key, tracks] of Object.entries(allLearningTracks)) {
if (!(key in allEnglishLearningTracks)) {
// This can happen when the translation of
// `data/learning-tracks/foo.yml` has stuff in it that the English
// content no longer has. In that case, just skip it.
delete allLearningTracks[key]
console.warn('No English learning track for %s', key)
continue
}
for (const [name, track] of Object.entries(tracks)) {
// If this individual track does no longer exist in English,
// delete it from the translation too.
if (!(name in allEnglishLearningTracks[key])) {
delete tracks[name]
continue
}
track.guides = allEnglishLearningTracks[key][name].guides
}
}
}
let tracksPerProduct = allLearningTracks[trackProduct]
// If there are no learning tracks for the current product, try and fall
// back to the learning track product set as a URL parameter. This handles
// the case where a learning track has guide paths for a different product
// than the current learning track product.
if (!tracksPerProduct) {
if (!req.query.learnProduct) return noTrack()
if (Array.isArray(req.query.learnProduct)) {
trackProduct = req.query.learnProduct[0] as string
} else {
trackProduct = req.query.learnProduct as string
}
tracksPerProduct = allLearningTracks[trackProduct]
}
if (!tracksPerProduct) return noTrack()
const track = allLearningTracks[trackProduct][trackName]
if (!track) return noTrack()
// The trackTitle comes from a data .yml file and may use Liquid templating, so we need to render it
const renderOpts = { textOnly: true }
// Some translated titles are known to have broken Liquid, so we need to
// try rendering them in English as a fallback.
const trackTitle = await executeWithFallback(
req.context,
() => renderContent(track.title, req.context, renderOpts),
() => '', // todo use english track.title
)
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 trackGuidePaths = trackGuides.map((guide) => {
return getPathWithoutLanguage(getPathWithoutVersion(guide.href))
})
let guideIndex = trackGuidePaths.findIndex((path) => path === guidePath)
// The learning track path may use Liquid version conditionals, handle the
// case where the requested path is a learning track path but won't match
// because of a Liquid conditional.
if (guideIndex < 0) {
guideIndex = await indexOfLearningTrackGuide(trackGuidePaths, guidePath, req.context)
}
// Also check if the learning track path is now a redirect to the requested
// page, we still want to render the learning track banner in that case.
// Also handles Liquid conditionals in the track path.
if (guideIndex < 0) {
for (const redirect of req.context.page.redirect_from || []) {
if (guideIndex >= 0) break
guideIndex = await indexOfLearningTrackGuide(trackGuidePaths, redirect, req.context)
}
}
if (guideIndex < 0) return noTrack()
currentLearningTrack.numberOfGuides = trackGuidePaths.length
currentLearningTrack.currentGuideIndex = guideIndex
if (guideIndex > 0) {
const prevGuidePath = trackGuidePaths[guideIndex - 1]
const resultData = await getLinkData(prevGuidePath, req.context, {
title: true,
intro: false,
fullTitle: false,
})
if (!resultData || !resultData.length) return noTrack()
const result = resultData[0]
const href = result.href
const title = result.title
currentLearningTrack.prevGuide = { href, title }
}
if (guideIndex < trackGuidePaths.length - 1) {
const nextGuidePath = trackGuidePaths[guideIndex + 1]
const resultData = await getLinkData(nextGuidePath, req.context, {
title: true,
intro: false,
fullTitle: false,
})
if (!resultData || !resultData.length) return noTrack()
const result = resultData[0]
const href = result.href
const title = result.title
currentLearningTrack.nextGuide = { href, title }
}
req.context.currentLearningTrack = currentLearningTrack
return next()
}
// Find the index of a learning track guide path in an array of guide paths,
// return -1 if not found.
async function indexOfLearningTrackGuide(
trackGuidePaths: string[],
guidePath: string,
context: Context,
) {
let guideIndex = -1
const renderOpts = { textOnly: true }
for (let i = 0; i < trackGuidePaths.length; i++) {
// Learning track URLs may have Liquid conditionals.
const renderedGuidePath = await executeWithFallback(
context,
() => renderContent(trackGuidePaths[i], context, renderOpts),
() => '', // todo use english trackGuidePaths[i]
)
if (!renderedGuidePath) continue
if (renderedGuidePath === guidePath) {
guideIndex = i
break
}
}
return guideIndex
}