@@ -51,6 +51,23 @@ interface ObfButton {
5151 image_id ?: string ; // Reference to image in the images array
5252}
5353
54+ interface ObfManifest {
55+ root ?: string ;
56+ paths ?: {
57+ boards ?: { [ key : string ] : string } ;
58+ images ?: { [ key : string ] : string | ObfImage } ;
59+ sounds ?: { [ key : string ] : string } ;
60+ } ;
61+ }
62+
63+ interface ObfImage {
64+ id : number ;
65+ path : string ;
66+ width : number ;
67+ height : number ;
68+ content_type : string ;
69+ }
70+
5471/**
5572 * Map OBF hidden value to AAC standard visibility
5673 * OBF: true = hidden, false/undefined = visible
@@ -201,15 +218,14 @@ class ObfProcessor extends BaseProcessor {
201218 }
202219 }
203220
204- private async processBoard ( boardData : ObfBoard , _boardPath : string ) : Promise < AACPage > {
221+ private async processBoard (
222+ boardData : ObfBoard ,
223+ _boardPath : string ,
224+ isZipEntry : boolean
225+ ) : Promise < AACPage > {
205226 const sourceButtons = boardData . buttons || [ ] ;
206227
207228 // Calculate page ID first (used to make button IDs unique)
208- const isZipEntry =
209- _boardPath &&
210- _boardPath . endsWith ( '.obf' ) &&
211- ! _boardPath . includes ( '/' ) &&
212- ! _boardPath . includes ( '\\' ) ;
213229 const pageId = isZipEntry
214230 ? _boardPath // Zip entry - use filename to match navigation paths
215231 : boardData ?. id
@@ -437,7 +453,7 @@ class ObfProcessor extends BaseProcessor {
437453 const boardData = tryParseObfJson ( content ) ;
438454 if ( boardData ) {
439455 console . log ( '[OBF] Detected .obf file, parsed as JSON' ) ;
440- const page = await this . processBoard ( boardData , filePathOrBuffer ) ;
456+ const page = await this . processBoard ( boardData , filePathOrBuffer , false ) ;
441457 tree . addPage ( page ) ;
442458
443459 // Set metadata from root board
@@ -464,7 +480,7 @@ class ObfProcessor extends BaseProcessor {
464480 const asJson = tryParseObfJson ( filePathOrBuffer ) ;
465481 if ( asJson ) {
466482 console . log ( '[OBF] Detected buffer/string as OBF JSON' ) ;
467- const page = await this . processBoard ( asJson , '[bufferOrString]' ) ;
483+ const page = await this . processBoard ( asJson , '[bufferOrString]' , false ) ;
468484 tree . addPage ( page ) ;
469485
470486 // Set metadata from root board
@@ -511,18 +527,43 @@ class ObfProcessor extends BaseProcessor {
511527
512528 console . log ( '[OBF] Detected zip archive, extracting .obf files' ) ;
513529
514- // Collect all .obf entries
515- const obfEntries = this . zipFile
516- . listFiles ( )
517- . filter ( ( name ) => name . toLowerCase ( ) . endsWith ( '.obf' ) ) ;
530+ // List manifest and OBF files
531+ const filesInZip = this . zipFile . listFiles ( ) ;
532+ const manifestFile = filesInZip . filter ( ( name ) => name . toLowerCase ( ) === 'manifest.json' ) ;
533+ let obfEntries = filesInZip . filter ( ( name ) => name . toLowerCase ( ) . endsWith ( '.obf' ) ) ;
534+
535+ // Attempt to read manifest
536+ if ( manifestFile && manifestFile . length === 1 ) {
537+ try {
538+ const content = await this . zipFile . readFile ( manifestFile [ 0 ] ) ;
539+ const data = decodeText ( content ) ;
540+ const str = typeof data === 'string' ? data : readTextFromInput ( data ) ;
541+ if ( ! str . trim ( ) ) throw new Error ( 'Manifest object missing' ) ;
542+ const manifestObject = JSON . parse ( str ) as ObfManifest ;
543+ if ( ! manifestObject ) throw new Error ( 'Manifest object is empty' ) ;
544+
545+ // Replace OBF file list
546+ if ( manifestObject . paths && manifestObject . paths . boards ) {
547+ obfEntries = Object . values ( manifestObject . paths . boards ) ;
548+ }
549+
550+ // Move root board to top of list
551+ if ( manifestObject . root ) {
552+ obfEntries = obfEntries . filter ( ( item ) => item !== manifestObject . root ) ;
553+ obfEntries . unshift ( manifestObject . root ) ;
554+ }
555+ } catch ( err ) {
556+ console . warn ( '[OBF] Error processing mainfest' , err ) ;
557+ }
558+ }
518559
519560 // Process each .obf entry
520561 for ( const entryName of obfEntries ) {
521562 try {
522563 const content = await this . zipFile . readFile ( entryName ) ;
523564 const boardData = tryParseObfJson ( decodeText ( content ) ) ;
524565 if ( boardData ) {
525- const page = await this . processBoard ( boardData , entryName ) ;
566+ const page = await this . processBoard ( boardData , entryName , true ) ;
526567 tree . addPage ( page ) ;
527568
528569 // Set metadata if not already set (use first board as reference)
0 commit comments