diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 01c13f3..7df1473 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ but intended to evolve over time with _community_ contributions. Trying to figure out how to make it work? Or how to use it in your scenario? -1. Review the [README](README.md) +1. Review the [README](https://github.com/FortAwesome/react-fontawesome?tab=readme-ov-file) 2. Get familiar with the documentation for the [SVG with JavaScript](https://fontawesome.com/how-to-use/svg-with-js) implementation, the framework upon which this component is built. Sometimes the answers you need may be there. 3. Post any remaining questions on [StackOverflow](https://stackoverflow.com/questions/tagged/react-fontawesome) with the tag `react-fontawesome`. diff --git a/docs/src/API-Reference-Readme.md b/docs/src/API-Reference-Readme.md index 785859a..304242d 100644 --- a/docs/src/API-Reference-Readme.md +++ b/docs/src/API-Reference-Readme.md @@ -1,4 +1,4 @@ -# Overview +# Overview This documentation serves purely as an API Reference for the modules contained in the latest version of the `react-fontawesome` library. @@ -15,8 +15,8 @@ For more detailed documentation on how to use FontAwesome with React in general, In the side-bar menu, the top level items are the two key module entry points that are exported by the library: -- `index` - includes anything exported via the main entry point +- `main` - includes anything exported via the main entry point - e.g. `import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'` -- `components/rsc/CustomPrefixProvider` - special module for using custom FA prefixes with server-side rendering +- `CustomPrefixProvider` - special module for using custom FA prefixes with server-side rendering - e.g. `import { CustomPrefixProvider } from '@fortawesome/react-fontawesome/components/rsc/CustomPrefixProvider'` - `` - refers to types and interfaces that are used internally within the package but are not exported by the package to consumers diff --git a/package-lock.json b/package-lock.json index a295a32..88d5984 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9570,9 +9570,9 @@ } }, "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, diff --git a/src/components/rsc/CustomPrefixProvider.tsx b/src/components/rsc/CustomPrefixProvider.tsx index c493b46..737a323 100644 --- a/src/components/rsc/CustomPrefixProvider.tsx +++ b/src/components/rsc/CustomPrefixProvider.tsx @@ -1,3 +1,5 @@ +/** @module CustomPrefixProvider */ + 'use client' import { config } from '@fortawesome/fontawesome-svg-core' diff --git a/src/index.ts b/src/index.ts index 87f11f3..cd03cdc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +/** @module main */ export * from './components/FontAwesomeIcon' export * from './components/FontAwesomeLayers' export * from './types/animation-props' diff --git a/typedoc.config.mjs b/typedoc.config.mjs index 0cfa05f..9e6af96 100644 --- a/typedoc.config.mjs +++ b/typedoc.config.mjs @@ -1,10 +1,45 @@ +import { cp, mkdir, readdir } from 'node:fs/promises' +import path from 'node:path' +import fs from 'node:fs' import { JSX } from 'typedoc' /** - * Custom TypeDoc plugin to insert FontAwesome into the generated documentation. - * @type {(app: import('typedoc').Application) => void} + * Custom TypeDoc plugin. + * @param {import('typedoc').Application} app */ function customPlugin(app) { + /** + * Remove `.html` from internal links while preserving query/hash fragments. + * External links are left untouched. + * @param {string} href + */ + function toExtensionlessHref(href, ignoreAbsolute = true) { + // Ignore absolute/protocol links and special schemes. + if ( + ignoreAbsolute && + (/^(?:[a-z][a-z\d+.-]*:)?\/\//i.test(href) || + /^[a-z][a-z\d+.-]*:/i.test(href)) + ) { + return href + } + + const [withoutHash, hash = ''] = href.split('#', 2) + const [pathname, query = ''] = withoutHash.split('?', 2) + + let nextPath = pathname + if (nextPath.endsWith('/index.html')) { + nextPath = nextPath.slice(0, -'index.html'.length) + } else if (nextPath === 'index.html') { + nextPath = './' + } else if (nextPath.endsWith('.html')) { + nextPath = nextPath.slice(0, -'.html'.length) + } + + const rebuilt = `${nextPath}${query ? `?${query}` : ''}` + return `${rebuilt}${hash ? `#${hash}` : ''}` + } + + // Insert FontAwesome CSS into the head of the generated documentation app.renderer.hooks.on('head.end', (_context) => { return JSX.createElement('link', { rel: 'stylesheet', @@ -12,29 +47,92 @@ function customPlugin(app) { }) }) + // Insert FontAwesome JS into the body of the generated documentation app.renderer.hooks.on('body.end', (_context) => { return JSX.createElement('script', { src: 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@7.2.0/js/all.min.js', defer: true, }) }) + + // Rewrite generated internal links to extensionless paths. + app.renderer.on('endPage', (page) => { + if (!page.contents) { + return + } + + page.contents = page.contents.replaceAll( + /href="([^"]+)"/g, + (_match, href) => { + return `href="${toExtensionlessHref(href)}"` + }, + ) + }) + + app.renderer.postRenderAsyncJobs.push(async (event) => { + // Create `name/index.html` aliases for root-level `name.html` pages. + const rootEntries = await readdir(event.outputDirectory, { + withFileTypes: true, + }) + const rootHtmlFiles = rootEntries.filter( + (entry) => + entry.isFile() && + entry.name.endsWith('.html') && + entry.name !== 'index.html' && + entry.name !== '404.html', + ) + + await Promise.all( + rootHtmlFiles.map(async (entry) => { + const fileName = entry.name + const basename = fileName.slice(0, -'.html'.length) + const source = path.join(event.outputDirectory, fileName) + const targetDir = path.join(event.outputDirectory, basename) + const target = path.join(targetDir, 'index.html') + + await mkdir(targetDir, { recursive: true }) + await cp(source, target) + }), + ) + + // Rewrite the `.html` extension from the generated `sitemap.xml` as well. + const sitemapPath = path.join(event.outputDirectory, 'sitemap.xml') + try { + const sitemapContents = fs.readFileSync(sitemapPath).toString('utf-8') + const rewritten = sitemapContents + .toString() + .replaceAll(/([^<]+)<\/loc>/g, (_match, loc) => { + return `${toExtensionlessHref(loc, false)}` + }) + await fs.writeFile(sitemapPath, rewritten, () => {}) + } catch (err) { + // If the sitemap doesn't exist, ignore the error and continue. + if (err.code !== 'ENOENT') { + throw err + } + } + }) } /** @type {Partial & Partial & import('typedoc-plugin-umami-analytics').Config} */ const config = { + cleanOutputDir: true, commentStyle: 'all', customFooterHtml: 'Built with by the FontAwesome Team and community contributors.', entryPoints: ['./src/index.ts', './src/components/rsc/*.tsx'], - entryPointStrategy: 'resolve', + // entryPointStrategy: 'resolve', excludeExternals: true, excludeNotDocumented: false, // Ensure references from @fortawesome packages are not considered external and therefore excluded, e.g. svg-core & common-types (we want them included) externalPattern: ['**/node_modules/!(@fortawesome)/**'], + githubPages: true, gitRemote: 'origin', gitRevision: 'main', favicon: './docs/src/assets/favicon.ico', hideGenerator: true, + hostedBaseUrl: 'https://fortawesome.github.io/react-fontawesome/', + markdownLinkExternal: true, mergeModulesMergeMode: 'module', mergeModulesRenameDefaults: true, name: 'FontAwesome React API Reference', @@ -53,20 +151,26 @@ const config = { 'typedoc-plugin-umami-analytics', customPlugin, ], - projectDocuments: ['./CHANGELOG.md'], + projectDocuments: [ + './CHANGELOG.md', + './CONTRIBUTING.md', + './CODE_OF_CONDUCT.md', + ], readme: './docs/src/API-Reference-Readme.md', - router: 'structure', + router: 'structure-dir', sort: [ 'required-first', 'kind', 'instance-first', 'alphabetical-ignoring-documents', ], - tsconfig: './tsconfig.json', + sourceLinkExternal: true, theme: 'typedoc-github-theme', + tsconfig: './tsconfig.json', umamiScriptURL: 'https://analytics.charlesharwood.dev/script.js', umamiWebsiteID: 'd0b01ae4-f0b1-4f6c-8967-6a9c1504cd49', useFirstParagraphOfCommentAsSummary: true, + visibilityFilters: {}, } export default config