publish.js 21 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. const env = require( 'jsdoc/env' );
  2. const fs = require( 'jsdoc/fs' );
  3. const helper = require( 'jsdoc/util/templateHelper' );
  4. const logger = require( 'jsdoc/util/logger' );
  5. const path = require( 'jsdoc/path' );
  6. const { taffy } = require( '@jsdoc/salty' );
  7. const template = require( 'jsdoc/template' );
  8. const util = require( 'util' );
  9. const htmlsafe = helper.htmlsafe;
  10. const linkto = helper.linkto;
  11. const resolveAuthorLinks = helper.resolveAuthorLinks;
  12. const hasOwnProp = Object.prototype.hasOwnProperty;
  13. let data;
  14. let view;
  15. const outdir = path.normalize( env.opts.destination );
  16. const themeOpts = ( env.opts.themeOpts ) || {};
  17. const categoryMap = {}; // Maps class names to their categories (Core, Addons, TSL)
  18. function mkdirSync( filepath ) {
  19. return fs.mkdirSync( filepath, { recursive: true } );
  20. }
  21. function find( spec ) {
  22. return helper.find( data, spec );
  23. }
  24. function getAncestorLinks( doclet ) {
  25. return helper.getAncestorLinks( data, doclet );
  26. }
  27. function hashToLink( doclet, hash ) {
  28. let url;
  29. if ( ! /^(#.+)/.test( hash ) ) {
  30. return hash;
  31. }
  32. url = helper.createLink( doclet );
  33. url = url.replace( /(#.+|$)/, hash );
  34. return `<a href="${url}">${hash}</a>`;
  35. }
  36. function needsSignature( { kind, type, meta } ) {
  37. let needsSig = false;
  38. // function and class definitions always get a signature
  39. if ( kind === 'function' || kind === 'class' ) {
  40. needsSig = true;
  41. } else if ( kind === 'typedef' && type && type.names && type.names.length ) {
  42. // typedefs that contain functions get a signature, too
  43. for ( let i = 0, l = type.names.length; i < l; i ++ ) {
  44. if ( type.names[ i ].toLowerCase() === 'function' ) {
  45. needsSig = true;
  46. break;
  47. }
  48. }
  49. } else if ( kind === 'namespace' && meta && meta.code && meta.code.type && meta.code.type.match( /[Ff]unction/ ) ) {
  50. // and namespaces that are functions get a signature (but finding them is a
  51. // bit messy)
  52. needsSig = true;
  53. }
  54. return needsSig;
  55. }
  56. function updateItemName( item ) {
  57. let itemName = item.name || '';
  58. if ( item.variable ) {
  59. itemName = `&hellip;${itemName}`;
  60. }
  61. return itemName;
  62. }
  63. function addParamAttributes( params ) {
  64. return params.filter( ( { name } ) => name && ! name.includes( '.' ) ).map( param => {
  65. let itemName = updateItemName( param );
  66. if ( param.type && param.type.names && param.type.names.length ) {
  67. const escapedTypes = param.type.names.map( name => linkto( name, htmlsafe( name ) ) );
  68. itemName += ' : <span class="param-type">' + escapedTypes.join( ' | ' ) + '</span>';
  69. }
  70. return itemName;
  71. } );
  72. }
  73. function buildItemTypeStrings( item ) {
  74. const types = [];
  75. if ( item && item.type && item.type.names ) {
  76. item.type.names.forEach( name => {
  77. types.push( linkto( name, htmlsafe( name ) ) );
  78. } );
  79. }
  80. return types;
  81. }
  82. function buildSearchListForData() {
  83. const categories = {
  84. 'Core': [],
  85. 'Addons': [],
  86. 'Global': [],
  87. 'TSL': []
  88. };
  89. // State flags that should appear in search (not type-checking properties)
  90. const allowedIsProperties = new Set( [
  91. 'isPlaying',
  92. 'isDisposed',
  93. 'isPresenting'
  94. ] );
  95. data().each( ( item ) => {
  96. // Skip .is* type-checking properties (e.g., isCamera, isMesh)
  97. // but keep state flags listed in allowedIsProperties
  98. if ( item.name && item.kind === 'member' ) {
  99. const name = item.name;
  100. if ( name.startsWith( 'is' ) && ! allowedIsProperties.has( name ) ) {
  101. return;
  102. }
  103. }
  104. if ( item.kind !== 'package' && item.kind !== 'typedef' && ! item.inherited ) {
  105. // Extract the class name from the longname (e.g., "Animation#getAnimationLoop" -> "Animation")
  106. const parts = item.longname.split( /[#~]/ );
  107. const className = parts[ 0 ];
  108. // If this item is a member/method of a class, check if the parent class exists
  109. if ( parts.length > 1 ) {
  110. // Find the parent class/module
  111. const parentClass = find( { longname: className, kind: [ 'class', 'module' ] } );
  112. // Only include if parent exists and is not private
  113. if ( parentClass && parentClass.length > 0 && parentClass[ 0 ].access !== 'private' ) {
  114. const category = categoryMap[ className ];
  115. const entry = {
  116. title: item.longname,
  117. kind: item.kind
  118. };
  119. if ( category ) {
  120. categories[ category ].push( entry );
  121. }
  122. }
  123. } else {
  124. // This is a top-level class/module/function - include if not private
  125. if ( item.access !== 'private' ) {
  126. let category = categoryMap[ className ];
  127. // If not in categoryMap, determine category from @tsl tag
  128. if ( ! category ) {
  129. const hasTslTag = Array.isArray( item.tags ) && item.tags.some( tag => tag.title === 'tsl' );
  130. if ( hasTslTag ) {
  131. category = 'TSL';
  132. } else {
  133. category = 'Global';
  134. }
  135. }
  136. const entry = {
  137. title: item.longname,
  138. kind: item.kind
  139. };
  140. categories[ category ].push( entry );
  141. }
  142. }
  143. }
  144. } );
  145. return categories;
  146. }
  147. function buildAttribsString( attribs ) {
  148. let attribsString = '';
  149. if ( attribs && attribs.length ) {
  150. attribsString = htmlsafe( util.format( '(%s) ', attribs.join( ', ' ) ) );
  151. }
  152. return attribsString;
  153. }
  154. function addNonParamAttributes( items ) {
  155. let types = [];
  156. items.forEach( item => {
  157. types = types.concat( buildItemTypeStrings( item ) );
  158. } );
  159. return types;
  160. }
  161. function addSignatureParams( f ) {
  162. const params = f.params ? addParamAttributes( f.params ) : [];
  163. const paramsString = params.join( ', ' );
  164. f.signature = util.format( '%s(%s)', ( f.signature || '' ), paramsString ? ' ' + paramsString + ' ' : '' );
  165. }
  166. function addSignatureReturns( f ) {
  167. let returnTypes = [];
  168. let returnTypesString = '';
  169. const source = f.yields || f.returns;
  170. if ( source ) {
  171. returnTypes = addNonParamAttributes( source );
  172. }
  173. if ( returnTypes.length ) {
  174. returnTypesString = util.format( ' : %s', returnTypes.join( ' | ' ) );
  175. }
  176. f.signature = `<span class="signature">${f.signature || ''}</span>${returnTypesString ? `<span class="type-signature">${returnTypesString}</span>` : ''}`;
  177. }
  178. function addSignatureTypes( f ) {
  179. const types = f.type ? buildItemTypeStrings( f ) : [];
  180. f.signature = `${f.signature || ''}${types.length ? `<span class="type-signature"> : ${types.join( ' | ' )}</span>` : ''}`;
  181. }
  182. function addAttribs( f ) {
  183. const attribs = helper.getAttribs( f ).filter( attrib => attrib !== 'static' && attrib !== 'nullable' );
  184. const attribsString = buildAttribsString( attribs );
  185. f.attribs = attribsString ? util.format( '<span class="type-signature">%s</span>', attribsString ) : '';
  186. }
  187. function shortenPaths( files, commonPrefix ) {
  188. Object.keys( files ).forEach( file => {
  189. files[ file ].shortened = files[ file ].resolved.replace( commonPrefix, '' )
  190. // always use forward slashes
  191. .replace( /\\/g, '/' );
  192. } );
  193. return files;
  194. }
  195. function getPathFromDoclet( { meta } ) {
  196. if ( ! meta ) {
  197. return null;
  198. }
  199. return meta.path && meta.path !== 'null' ?
  200. path.join( meta.path, meta.filename ) :
  201. meta.filename;
  202. }
  203. function getFullAugmentsChain( doclet ) {
  204. const chain = [];
  205. if ( ! doclet || ! doclet.augments || ! doclet.augments.length ) {
  206. return chain;
  207. }
  208. // Start with the immediate parent
  209. const parentName = doclet.augments[ 0 ];
  210. chain.push( parentName );
  211. // Recursively find the parent's ancestors
  212. const parentDoclet = find( { longname: parentName } );
  213. if ( parentDoclet && parentDoclet.length > 0 ) {
  214. const parentChain = getFullAugmentsChain( parentDoclet[ 0 ] );
  215. chain.unshift( ...parentChain );
  216. }
  217. return chain;
  218. }
  219. function generate( title, docs, filename, resolveLinks ) {
  220. let html;
  221. resolveLinks = resolveLinks !== false;
  222. const docData = {
  223. env: env,
  224. title: title,
  225. docs: docs,
  226. augments: docs && docs[ 0 ] ? getFullAugmentsChain( docs[ 0 ] ) : null
  227. };
  228. // Put HTML files in pages/ subdirectory
  229. const pagesDir = path.join( outdir, 'pages' );
  230. mkdirSync( pagesDir );
  231. const outpath = path.join( pagesDir, filename );
  232. html = view.render( 'container.tmpl', docData );
  233. if ( resolveLinks ) {
  234. html = helper.resolveLinks( html ); // turn {@link foo} into <a href="foodoc.html">foo</a>
  235. }
  236. // Convert Prettify classes to Highlight.js format
  237. html = html.replace( /<pre class="prettyprint source linenums"><code>/g, '<pre><code>' );
  238. html = html.replace( /<pre class="prettyprint source lang-(\w+)"[^>]*><code>/g, '<pre><code class="language-$1">' );
  239. html = html.replace( /<pre class="prettyprint"><code>/g, '<pre><code>' );
  240. // Add target="_blank" to external links
  241. html = html.replace( /<a\s+([^>]*href=["'](https?:\/\/[^"']+)["'][^>]*)>/gi, '<a $1 target="_blank" rel="noopener">' );
  242. // Remove lines that only contain whitespace
  243. html = html.replace( /^\s*\n/gm, '' );
  244. fs.writeFileSync( outpath, html, 'utf8' );
  245. }
  246. function generateSourceFiles( sourceFiles, encoding = 'utf8' ) {
  247. Object.keys( sourceFiles ).forEach( file => {
  248. let source;
  249. // links are keyed to the shortened path in each doclet's `meta.shortpath` property
  250. const sourceOutfile = helper.getUniqueFilename( sourceFiles[ file ].shortened );
  251. helper.registerLink( sourceFiles[ file ].shortened, sourceOutfile );
  252. try {
  253. source = {
  254. kind: 'source',
  255. code: helper.htmlsafe( fs.readFileSync( sourceFiles[ file ].resolved, encoding ) )
  256. };
  257. } catch ( e ) {
  258. logger.error( 'Error while generating source file %s: %s', file, e.message );
  259. }
  260. generate( `Source: ${sourceFiles[ file ].shortened}`, [ source ], sourceOutfile,
  261. false );
  262. } );
  263. }
  264. function buildMainNav( items, itemsSeen, linktoFn ) {
  265. const coreDirectory = 'src';
  266. const addonsDirectory = 'examples/jsm';
  267. const hierarchy = new Map();
  268. hierarchy.set( 'Core', new Map() );
  269. hierarchy.set( 'Addons', new Map() );
  270. let nav = '';
  271. if ( items.length ) {
  272. items.forEach( item => {
  273. let displayName;
  274. let itemNav = '';
  275. if ( ! hasOwnProp.call( itemsSeen, item.longname ) ) {
  276. if ( env.conf.templates.default.useLongnameInNav ) {
  277. displayName = item.longname;
  278. } else {
  279. displayName = item.name;
  280. }
  281. itemNav += `<li>${linktoFn( item.longname, displayName.replace( /\b(module|event):/g, '' ) )}</li>\n`;
  282. itemsSeen[ item.longname ] = true;
  283. const path = item.meta.shortpath;
  284. if ( path.startsWith( coreDirectory ) ) {
  285. const subCategory = path.split( '/' )[ 1 ];
  286. pushNavItem( hierarchy, 'Core', subCategory, itemNav );
  287. categoryMap[ item.longname ] = 'Core';
  288. } else if ( path.startsWith( addonsDirectory ) ) {
  289. const subCategory = path.split( '/' )[ 2 ];
  290. pushNavItem( hierarchy, 'Addons', subCategory, itemNav );
  291. categoryMap[ item.longname ] = 'Addons';
  292. }
  293. }
  294. } );
  295. for ( const [ mainCategory, map ] of hierarchy ) {
  296. nav += `\t\t\t\t\t<h2>${mainCategory}</h2>\n`;
  297. const sortedMap = new Map( [ ...map.entries() ].sort() ); // sort sub categories
  298. for ( const [ subCategory, links ] of sortedMap ) {
  299. nav += `\t\t\t\t\t<h3>${subCategory}</h3>\n`;
  300. nav += '\t\t\t\t\t<ul>\n';
  301. links.sort();
  302. for ( const link of links ) {
  303. nav += '\t\t\t\t\t\t' + link;
  304. }
  305. nav += '\t\t\t\t\t</ul>\n';
  306. }
  307. }
  308. }
  309. return nav;
  310. }
  311. function buildGlobalsNav( globals, seen ) {
  312. let globalNav;
  313. let nav = '';
  314. if ( globals.length ) {
  315. // TSL
  316. let tslNav = '';
  317. globals.forEach( ( { kind, longname, name, tags } ) => {
  318. if ( kind !== 'typedef' && ! hasOwnProp.call( seen, longname ) ) {
  319. const hasTslTag = Array.isArray( tags ) && tags.some( tag => tag.title === 'tsl' );
  320. if ( hasTslTag ) {
  321. tslNav += `\t\t\t\t\t\t<li>${linkto( longname, name )}</li>\n`;
  322. seen[ longname ] = true;
  323. }
  324. }
  325. } );
  326. nav += '\t\t\t\t\t<h2>TSL</h2>\n';
  327. nav += '\t\t\t\t\t<ul>\n';
  328. nav += tslNav;
  329. nav += '\t\t\t\t\t</ul>\n';
  330. // Globals
  331. globalNav = '';
  332. globals.forEach( ( { kind, longname, name } ) => {
  333. if ( kind !== 'typedef' && ! hasOwnProp.call( seen, longname ) ) {
  334. globalNav += `\t\t\t\t\t\t<li>${linkto( longname, name )}</li>\n`;
  335. }
  336. seen[ longname ] = true;
  337. } );
  338. if ( ! globalNav ) {
  339. // turn the heading into a link so you can actually get to the global page
  340. nav += `\t\t\t\t\t<h3>${linkto( 'global', 'Global' )}</h3>\n`;
  341. } else {
  342. nav += '\t\t\t\t\t<h2>Global</h2>\n';
  343. nav += '\t\t\t\t\t<ul>\n';
  344. nav += globalNav;
  345. nav += '\t\t\t\t\t</ul>\n';
  346. }
  347. }
  348. return nav;
  349. }
  350. function pushNavItem( hierarchy, mainCategory, subCategory, itemNav ) {
  351. // Special case for TSL - keep it all uppercase
  352. if ( subCategory.toLowerCase() === 'tsl' ) {
  353. subCategory = 'TSL';
  354. } else {
  355. subCategory = subCategory[ 0 ].toUpperCase() + subCategory.slice( 1 ); // capitalize
  356. }
  357. if ( hierarchy.get( mainCategory ).get( subCategory ) === undefined ) {
  358. hierarchy.get( mainCategory ).set( subCategory, [] );
  359. }
  360. const categoryList = hierarchy.get( mainCategory ).get( subCategory );
  361. categoryList.push( itemNav );
  362. }
  363. /**
  364. * Create the navigation sidebar.
  365. * @param {Object} members The members that will be used to create the sidebar.
  366. * @return {string} The HTML for the navigation sidebar.
  367. */
  368. function buildNav( members ) {
  369. let nav = '\n';
  370. const seen = {};
  371. nav += buildMainNav( [ ...members.classes, ...members.modules ], seen, linkto );
  372. nav += buildGlobalsNav( members.globals, seen );
  373. return nav;
  374. }
  375. /**
  376. @param {TAFFY} taffyData See <http://taffydb.com/>.
  377. @param {Object} opts
  378. @param {Tutorial} tutorials
  379. */
  380. exports.publish = ( taffyData, opts, tutorials ) => {
  381. const sourceFilePaths = [];
  382. let sourceFiles = {};
  383. let staticFileFilter;
  384. let staticFilePaths;
  385. let staticFileScanner;
  386. data = taffyData;
  387. const conf = env.conf.templates || {};
  388. conf.default = conf.default || {};
  389. const templatePath = path.normalize( opts.template );
  390. view = new template.Template( path.join( templatePath, 'tmpl' ) );
  391. // claim some special filenames in advance, so the All-Powerful Overseer of Filename Uniqueness
  392. // doesn't try to hand them out later
  393. const indexUrl = helper.getUniqueFilename( 'index' );
  394. // don't call registerLink() on this one! 'index' is also a valid longname
  395. const globalUrl = helper.getUniqueFilename( 'global' );
  396. helper.registerLink( 'global', globalUrl );
  397. // set up templating
  398. view.layout = conf.default.layoutFile ?
  399. path.getResourcePath( path.dirname( conf.default.layoutFile ),
  400. path.basename( conf.default.layoutFile ) ) :
  401. 'layout.tmpl';
  402. // set up tutorials for helper
  403. helper.setTutorials( tutorials );
  404. data = helper.prune( data );
  405. data.sort( 'longname, version, since' );
  406. helper.addEventListeners( data );
  407. data().each( doclet => {
  408. let sourcePath;
  409. doclet.attribs = '';
  410. if ( doclet.see ) {
  411. doclet.see.forEach( ( seeItem, i ) => {
  412. doclet.see[ i ] = hashToLink( doclet, seeItem );
  413. } );
  414. }
  415. // build a list of source files
  416. if ( doclet.meta ) {
  417. sourcePath = getPathFromDoclet( doclet );
  418. sourceFiles[ sourcePath ] = {
  419. resolved: sourcePath,
  420. shortened: null
  421. };
  422. if ( ! sourceFilePaths.includes( sourcePath ) ) {
  423. sourceFilePaths.push( sourcePath );
  424. }
  425. }
  426. } );
  427. fs.mkPath( outdir );
  428. // copy the template's static files to outdir
  429. const fromDir = path.join( templatePath, 'static' );
  430. const staticFiles = fs.ls( fromDir, 3 );
  431. staticFiles.forEach( fileName => {
  432. const toDir = fs.toDir( fileName.replace( fromDir, outdir ) );
  433. fs.mkPath( toDir );
  434. fs.copyFileSync( fileName, toDir );
  435. } );
  436. // copy user-specified static files to outdir
  437. if ( conf.default.staticFiles ) {
  438. // The canonical property name is `include`. We accept `paths` for backwards compatibility
  439. // with a bug in JSDoc 3.2.x.
  440. staticFilePaths = conf.default.staticFiles.include ||
  441. conf.default.staticFiles.paths ||
  442. [];
  443. staticFileFilter = new ( require( 'jsdoc/src/filter' ).Filter )( conf.default.staticFiles );
  444. staticFileScanner = new ( require( 'jsdoc/src/scanner' ).Scanner )();
  445. staticFilePaths.forEach( filePath => {
  446. filePath = path.resolve( env.pwd, filePath );
  447. const extraStaticFiles = staticFileScanner.scan( [ filePath ], 10, staticFileFilter );
  448. extraStaticFiles.forEach( fileName => {
  449. const sourcePath = fs.toDir( filePath );
  450. const toDir = fs.toDir( fileName.replace( sourcePath, outdir ) );
  451. fs.mkPath( toDir );
  452. fs.copyFileSync( fileName, toDir );
  453. } );
  454. } );
  455. }
  456. if ( sourceFilePaths.length ) {
  457. sourceFiles = shortenPaths( sourceFiles, path.commonPrefix( sourceFilePaths ) );
  458. }
  459. data().each( doclet => {
  460. let docletPath;
  461. const url = helper.createLink( doclet );
  462. helper.registerLink( doclet.longname, url );
  463. // add a shortened version of the full path
  464. if ( doclet.meta ) {
  465. docletPath = getPathFromDoclet( doclet );
  466. docletPath = sourceFiles[ docletPath ].shortened;
  467. if ( docletPath ) {
  468. doclet.meta.shortpath = docletPath;
  469. }
  470. }
  471. } );
  472. data().each( doclet => {
  473. const url = helper.longnameToUrl[ doclet.longname ];
  474. if ( url.includes( '#' ) ) {
  475. doclet.id = helper.longnameToUrl[ doclet.longname ].split( /#/ ).pop();
  476. } else {
  477. doclet.id = doclet.name;
  478. }
  479. if ( needsSignature( doclet ) ) {
  480. addSignatureParams( doclet );
  481. addSignatureReturns( doclet );
  482. addAttribs( doclet );
  483. }
  484. } );
  485. // do this after the urls have all been generated
  486. data().each( doclet => {
  487. doclet.ancestors = getAncestorLinks( doclet );
  488. if ( doclet.kind === 'member' ) {
  489. addSignatureTypes( doclet );
  490. addAttribs( doclet );
  491. }
  492. if ( doclet.kind === 'constant' ) {
  493. addSignatureTypes( doclet );
  494. addAttribs( doclet );
  495. doclet.kind = 'member';
  496. }
  497. } );
  498. // prepare import statements, demo tags, and extract code examples
  499. data().each( doclet => {
  500. if ( doclet.kind === 'class' || doclet.kind === 'module' ) {
  501. const tags = doclet.tags;
  502. if ( Array.isArray( tags ) ) {
  503. const importTag = tags.find( tag => tag.title === 'three_import' );
  504. doclet.import = ( importTag !== undefined ) ? importTag.text : null;
  505. const demoTag = tags.find( tag => tag.title === 'demo' );
  506. doclet.demo = ( demoTag !== undefined ) ? demoTag.text : null;
  507. }
  508. // Extract code example from classdesc
  509. if ( doclet.classdesc ) {
  510. const codeBlockRegex = /<pre class="prettyprint source[^"]*"><code>([\s\S]*?)<\/code><\/pre>/;
  511. const match = doclet.classdesc.match( codeBlockRegex );
  512. if ( match ) {
  513. doclet.codeExample = match[ 0 ];
  514. // Remove the code example from classdesc
  515. doclet.classdesc = doclet.classdesc.replace( codeBlockRegex, '' ).trim();
  516. }
  517. }
  518. }
  519. } );
  520. const members = helper.getMembers( data );
  521. members.tutorials = tutorials.children;
  522. // output pretty-printed source files by default
  523. const outputSourceFiles = conf.default && conf.default.outputSourceFiles !== false;
  524. // add template helpers
  525. view.find = find;
  526. view.linkto = linkto;
  527. view.resolveAuthorLinks = resolveAuthorLinks;
  528. view.htmlsafe = htmlsafe;
  529. view.outputSourceFiles = outputSourceFiles;
  530. view.ignoreInheritedSymbols = themeOpts.ignoreInheritedSymbols;
  531. // Empty nav in templates - will be loaded from nav.html client-side
  532. view.nav = '';
  533. // generate the pretty-printed source files first so other pages can link to them
  534. if ( outputSourceFiles ) {
  535. generateSourceFiles( sourceFiles, opts.encoding );
  536. }
  537. if ( members.globals.length ) {
  538. // Split globals into TSL and non-TSL
  539. const tslGlobals = [];
  540. const nonTslGlobals = [];
  541. const originalGlobals = members.globals;
  542. originalGlobals.forEach( item => {
  543. const hasTslTag = Array.isArray( item.tags ) && item.tags.some( tag => tag.title === 'tsl' );
  544. if ( hasTslTag ) {
  545. tslGlobals.push( item );
  546. // Register each TSL item to link to TSL.html
  547. helper.registerLink( item.longname, 'TSL.html#' + item.name );
  548. } else {
  549. nonTslGlobals.push( item );
  550. }
  551. } );
  552. // Generate TSL.html for TSL functions
  553. if ( tslGlobals.length ) {
  554. generate( 'TSL', [ { kind: 'globalobj', isTSL: true } ], 'TSL.html' );
  555. }
  556. // Generate global.html for remaining globals
  557. if ( nonTslGlobals.length ) {
  558. generate( 'Global', [ { kind: 'globalobj' } ], globalUrl );
  559. }
  560. }
  561. // index page displays information from package.json and lists files
  562. const files = find( { kind: 'file' } );
  563. const packages = find( { kind: 'package' } );
  564. generate( '', // MODIFIED (Remove Home title)
  565. packages.concat(
  566. [ {
  567. kind: 'mainpage',
  568. readme: opts.readme,
  569. longname: ( opts.mainpagetitle ) ? opts.mainpagetitle : 'Main Page'
  570. } ]
  571. ).concat( files ), indexUrl );
  572. // set up the lists that we'll use to generate pages
  573. const classes = taffy( members.classes );
  574. const modules = taffy( members.modules );
  575. Object.keys( helper.longnameToUrl ).forEach( longname => {
  576. const myClasses = helper.find( classes, { longname: longname } );
  577. const myModules = helper.find( modules, { longname: longname } );
  578. if ( myClasses.length ) {
  579. generate( `${myClasses[ 0 ].name}`, myClasses, helper.longnameToUrl[ longname ] );
  580. }
  581. if ( myModules.length ) {
  582. generate( `${myModules[ 0 ].name}`, myModules, helper.longnameToUrl[ longname ] );
  583. }
  584. } );
  585. // Build navigation HTML
  586. const navHtml = buildNav( members );
  587. // Generate index.html with embedded navigation
  588. const indexTemplatePath = path.join( templatePath, 'static', 'index.html' );
  589. let indexHtml = fs.readFileSync( indexTemplatePath, 'utf8' );
  590. // Replace placeholder with actual navigation
  591. indexHtml = indexHtml.replace( '<!--NAV_PLACEHOLDER-->', navHtml );
  592. fs.writeFileSync(
  593. path.join( outdir, 'index.html' ),
  594. indexHtml,
  595. 'utf8'
  596. );
  597. // search
  598. const searchList = buildSearchListForData();
  599. fs.writeFileSync(
  600. path.join( outdir, 'search.json' ),
  601. JSON.stringify( searchList, null, '\t' )
  602. );
  603. };
粤ICP备19079148号