@@ -177,16 +177,129 @@ function getOrCreateFolderChild(parent: ApiNavItem, segmentKey: string, orderHin
177177 return f ;
178178}
179179
180+ function compareNavSiblings ( a : ApiNavItem , b : ApiNavItem ) : number {
181+ if ( a . order !== b . order ) return a . order - b . order ;
182+ return a . title . localeCompare ( b . title ) ;
183+ }
184+
180185function sortTreeRecursive ( node : ApiNavItem ) : void {
181- node . children . sort ( ( a , b ) => {
182- if ( a . order !== b . order ) return a . order - b . order ;
183- return a . title . localeCompare ( b . title ) ;
184- } ) ;
186+ node . children . sort ( compareNavSiblings ) ;
185187 for ( const c of node . children ) {
186188 if ( c . type === "folder" ) sortTreeRecursive ( c ) ;
187189 }
188190}
189191
192+ /** Re-sort siblings after mutating folder `order` (e.g. Nav API overlay). */
193+ export function resortMenulinksDerivedTree ( items : ApiNavItem [ ] ) : void {
194+ items . sort ( compareNavSiblings ) ;
195+ for ( const c of items ) {
196+ if ( c . type === "folder" ) sortTreeRecursive ( c ) ;
197+ }
198+ }
199+
200+ export type NavFolderOverlayEntry = { order : number ; title : string } ;
201+
202+ /** Path under `navRoot`: `development/apis` (no leading slash, lowercased). */
203+ export function navHrefToFolderOverlayKey (
204+ href : string | undefined ,
205+ navRoot : string
206+ ) : string | null {
207+ const h = href ?. trim ( ) ;
208+ if ( ! h ) return null ;
209+ let pathname = h ;
210+ if ( / ^ h t t p s ? : \/ \/ / i. test ( h ) || h . startsWith ( "//" ) ) {
211+ try {
212+ pathname = new URL ( h . startsWith ( "//" ) ? `https:${ h } ` : h ) . pathname ;
213+ } catch {
214+ return null ;
215+ }
216+ }
217+ const p = pathname . replace ( / \/ + $ / , "" ) ;
218+ const root = navRoot . trim ( ) . replace ( / \/ + $ / , "" ) ;
219+ const normRoot = root . toLowerCase ( ) ;
220+ const idx = p . toLowerCase ( ) . indexOf ( normRoot ) ;
221+ if ( idx === - 1 ) return null ;
222+ const rest = p . slice ( idx + root . length ) . replace ( / ^ \/ + / , "" ) . replace ( / \/ + $ / , "" ) ;
223+ return rest . length > 0 ? rest . toLowerCase ( ) : null ;
224+ }
225+
226+ function collectNavFolderOverlay (
227+ nodes : ApiNavItem [ ] | undefined ,
228+ navRoot : string ,
229+ out : Map < string , NavFolderOverlayEntry >
230+ ) : void {
231+ if ( ! nodes ?. length ) return ;
232+ for ( const n of nodes ) {
233+ if ( n . type === "folder" ) {
234+ const key = navHrefToFolderOverlayKey ( n . href , navRoot ) ;
235+ if ( key ) {
236+ out . set ( key , {
237+ order : Number ( n . order ) || 0 ,
238+ title : ( n . title ?? "" ) . trim ( ) ,
239+ } ) ;
240+ }
241+ collectNavFolderOverlay ( n . children , navRoot , out ) ;
242+ }
243+ }
244+ }
245+
246+ /**
247+ * Folder path → CMS `order` and display title from `GET /api/v1/nav` (menulinks-only trees lack
248+ * Nav Tool folder metadata; synthetic titles would otherwise come from slug segments, e.g. "Apis").
249+ */
250+ export function extractNavApiFolderOverlay (
251+ navApiJson : unknown ,
252+ navRoot : string
253+ ) : Map < string , NavFolderOverlayEntry > {
254+ const out = new Map < string , NavFolderOverlayEntry > ( ) ;
255+ if ( ! navApiJson || typeof navApiJson !== "object" ) return out ;
256+ const children = ( navApiJson as { entity ?: { children ?: ApiNavItem [ ] } } ) . entity ?. children ;
257+ if ( Array . isArray ( children ) ) {
258+ collectNavFolderOverlay ( children , navRoot , out ) ;
259+ }
260+ return out ;
261+ }
262+
263+ /** Mutates menulinks-built folders whose `code` is `.nav.<segment>` (see {@link folderMarker}). */
264+ export function applyNavFolderOverlayToMenulinksTree (
265+ items : ApiNavItem [ ] ,
266+ overlay : Map < string , NavFolderOverlayEntry >
267+ ) : void {
268+ const walk = ( nodes : ApiNavItem [ ] , pathPrefix : string ) : void => {
269+ for ( const node of nodes ) {
270+ if ( node . type !== "folder" ) continue ;
271+ const code = node . code ?? "" ;
272+ if ( ! code . startsWith ( ".nav." ) ) {
273+ walk ( node . children ?? [ ] , pathPrefix ) ;
274+ continue ;
275+ }
276+ const seg = code . slice ( ".nav." . length ) ;
277+ const key = pathPrefix ? `${ pathPrefix } /${ seg } ` : seg ;
278+ const low = key . toLowerCase ( ) ;
279+ const meta = overlay . get ( low ) ;
280+ if ( meta ) {
281+ node . order = meta . order ;
282+ if ( meta . title ) node . title = meta . title ;
283+ }
284+ walk ( node . children ?? [ ] , low ) ;
285+ }
286+ } ;
287+ walk ( items , "" ) ;
288+ }
289+
290+ /** Same path pattern as legacy `getNavSections`: `/api/v1/nav/docs/nav?depth=…&languageId=…`. */
291+ export function buildNavApiUrl (
292+ dotcmsHost : string ,
293+ navPath : string ,
294+ depth : number ,
295+ languageId : number
296+ ) : string {
297+ const base = dotcmsHost . replace ( / \/ + $ / , "" ) ;
298+ const p = navPath . startsWith ( "/" ) ? navPath : `/${ navPath } ` ;
299+ const d = Math . max ( 1 , depth ) ;
300+ return `${ base } /api/v1/nav${ p } ?depth=${ d } &languageId=${ languageId } ` ;
301+ }
302+
190303/**
191304 * Build top-level section folders (ApiNavItem tree) from flat menulinks rows.
192305 */
0 commit comments