Compare commits
	
		
			8 Commits
		
	
	
		
			dfd89fbf26
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 06bf874b80 | ||
|  | 0335e0cedb | ||
|  | 7ccaa531e1 | ||
|  | 6ad3ad8044 | ||
|  | e31ed72957 | ||
|  | 7a0355aebf | ||
|  | 92416a03be | ||
|  | d8975d4473 | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -22,3 +22,6 @@ dist-ssr | ||||
| *.njsproj | ||||
| *.sln | ||||
| *.sw? | ||||
|  | ||||
| # Local Netlify folder | ||||
| .netlify | ||||
|   | ||||
| @@ -1,28 +1,42 @@ | ||||
| import js from '@eslint/js' | ||||
| import globals from 'globals' | ||||
| import reactHooks from 'eslint-plugin-react-hooks' | ||||
| import reactRefresh from 'eslint-plugin-react-refresh' | ||||
| import tseslint from 'typescript-eslint' | ||||
| import js from "@eslint/js"; | ||||
| import reactDOM from "eslint-plugin-react-dom"; | ||||
| import reactHooks from "eslint-plugin-react-hooks"; | ||||
| import reactRefresh from "eslint-plugin-react-refresh"; | ||||
| import reactX from "eslint-plugin-react-x"; | ||||
| import globals from "globals"; | ||||
| import tseslint from "typescript-eslint"; | ||||
|  | ||||
| export default tseslint.config( | ||||
|   { ignores: ['dist'] }, | ||||
|   { ignores: ["dist"] }, | ||||
|   { | ||||
|     extends: [js.configs.recommended, ...tseslint.configs.recommended], | ||||
|     files: ['**/*.{ts,tsx}'], | ||||
|     extends: [ | ||||
|       js.configs.recommended, | ||||
|       ...tseslint.configs.recommendedTypeChecked, | ||||
|       ...tseslint.configs.stylisticTypeChecked, | ||||
|     ], | ||||
|     files: ["**/*.{ts,tsx}"], | ||||
|     languageOptions: { | ||||
|       ecmaVersion: 2020, | ||||
|       globals: globals.browser, | ||||
|       parserOptions: { | ||||
|         project: ["./tsconfig.node.json", "./tsconfig.app.json"], | ||||
|         tsconfigRootDir: import.meta.dirname, | ||||
|       }, | ||||
|     }, | ||||
|     plugins: { | ||||
|       'react-hooks': reactHooks, | ||||
|       'react-refresh': reactRefresh, | ||||
|       "react-hooks": reactHooks, | ||||
|       "react-refresh": reactRefresh, | ||||
|       "react-x": reactX, | ||||
|       "react-dom": reactDOM, | ||||
|     }, | ||||
|     rules: { | ||||
|       ...reactHooks.configs.recommended.rules, | ||||
|       'react-refresh/only-export-components': [ | ||||
|         'warn', | ||||
|       ...reactX.configs["recommended-typescript"].rules, | ||||
|       ...reactDOM.configs.recommended.rules, | ||||
|       "react-refresh/only-export-components": [ | ||||
|         "warn", | ||||
|         { allowConstantExport: true }, | ||||
|       ], | ||||
|     }, | ||||
|   }, | ||||
| ) | ||||
|   } | ||||
| ); | ||||
|   | ||||
							
								
								
									
										19
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								index.html
									
									
									
									
									
								
							| @@ -7,7 +7,24 @@ | ||||
|     <title>NIST AI Glossary Browser</title> | ||||
|   </head> | ||||
|   <body> | ||||
|     <div id="root"></div> | ||||
|     <main> | ||||
|       <div id="root"></div> | ||||
|     </main> | ||||
|     <footer> | ||||
|       The content on this web site is based on Daniel Atherton, Reva Schwartz, | ||||
|       Peter C. Fontana, Patrick Hall (2023) The Language of Trustworthy AI: An | ||||
|       In-Depth Glossary of Terms. (National Institute of Standards and | ||||
|       Technology, Gaithersburg, MD), NIST Artificial Intelligence | ||||
|       <a href="https://doi.org/10.6028/NIST.AI.100-3" target="_blank" | ||||
|         >AI 100-3</a | ||||
|       >, republished courtesy of the National Institute of Standards and | ||||
|       Technology. | ||||
|       <a | ||||
|         href="https://www.nist.gov/open/copyright-fair-use-and-licensing-statements-srd-data-software-and-technical-series-publications#techpubs" | ||||
|         >License conditions</a | ||||
|       > | ||||
|       apply. | ||||
|     </footer> | ||||
|     <script type="module" src="/src/main.tsx"></script> | ||||
|   </body> | ||||
| </html> | ||||
|   | ||||
							
								
								
									
										21
									
								
								netlify.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								netlify.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| # example netlify.toml | ||||
| [build] | ||||
|   command = "npm run build" | ||||
|   functions = "netlify/functions" | ||||
|   publish = "dist" | ||||
|  | ||||
|   ## Uncomment to use this redirect for Single Page Applications like create-react-app. | ||||
|   ## Not needed for static site generators. | ||||
|   #[[redirects]] | ||||
|   #  from = "/*" | ||||
|   #  to = "/index.html" | ||||
|   #  status = 200 | ||||
|  | ||||
|   ## (optional) Settings for Netlify Dev | ||||
|   ## https://github.com/netlify/cli/blob/main/docs/netlify-dev.md#project-detection | ||||
|   #[dev] | ||||
|   #  command = "yarn start" # Command to start your dev server | ||||
|   #  port = 3000 # Port that the dev server will be listening on | ||||
|   #  publish = "dist" # Folder with the static content for _redirect file | ||||
|  | ||||
|   ## more info on configuring this file: https://ntl.fyi/file-based-build-config | ||||
							
								
								
									
										1131
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1131
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -22,10 +22,12 @@ | ||||
|     "@types/papaparse": "^5.3.16", | ||||
|     "@types/react": "^19.1.2", | ||||
|     "@types/react-dom": "^19.1.2", | ||||
|     "@vitejs/plugin-react": "^4.4.1", | ||||
|     "@vitejs/plugin-react-swc": "^3.9.0", | ||||
|     "eslint": "^9.25.0", | ||||
|     "eslint-plugin-react-dom": "^1.49.0", | ||||
|     "eslint-plugin-react-hooks": "^5.2.0", | ||||
|     "eslint-plugin-react-refresh": "^0.4.19", | ||||
|     "eslint-plugin-react-x": "^1.49.0", | ||||
|     "globals": "^16.0.0", | ||||
|     "typescript": "~5.8.3", | ||||
|     "typescript-eslint": "^8.30.1", | ||||
|   | ||||
							
								
								
									
										30
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								src/App.tsx
									
									
									
									
									
								
							| @@ -1,18 +1,18 @@ | ||||
| import { useState } from "react"; | ||||
| import AlphabeticalFilter from "./components/AlphabeticalFilter/AlphabeticalFilter"; | ||||
| import CitationEntry from "./components/CitationEntry/CitationEntry"; | ||||
| import GlossaryEntry from "./components/GlossaryEntry/GlossaryEntry"; | ||||
| import StringFilter from "./components/StringFilter/StringFilter"; | ||||
| import useGlossary from "./hooks/useGlossary"; | ||||
|  | ||||
| function App() { | ||||
|   const { data: glossary, error, isPending } = useGlossary(); | ||||
|   const { glossary, citations } = useGlossary(); | ||||
|  | ||||
|   const [alphabeticalFilter, setAlphabeticalFilter] = useState(""); | ||||
|   const [stringFilter, setStringFilter] = useState(""); | ||||
|   const [selectedCitation, setSelectedCitation] = useState<string | null>(null); | ||||
|  | ||||
|   if (isPending) return <div>Loading...</div>; | ||||
|   if (error) return <div>Error: {error.message}</div>; | ||||
|  | ||||
|   const entries = glossary?.definitions | ||||
|   const entries = glossary | ||||
|     .filter((entry) => { | ||||
|       if (!stringFilter) { | ||||
|         return true; | ||||
| @@ -30,16 +30,28 @@ function App() { | ||||
|     }); | ||||
|  | ||||
|   return ( | ||||
|     <div> | ||||
|     <> | ||||
|       <StringFilter onChange={setStringFilter} /> | ||||
|       <AlphabeticalFilter onChange={setAlphabeticalFilter} /> | ||||
|       {entries.length} entries found | ||||
|       <dl> | ||||
|       {selectedCitation && ( | ||||
|         <CitationEntry | ||||
|           citation={citations.find( | ||||
|             (citation) => citation.key === selectedCitation | ||||
|           )} | ||||
|           onClose={() => setSelectedCitation(null)} | ||||
|         /> | ||||
|       )} | ||||
|       <dl style={{ maxWidth: "min(100vw, 64ch)" }}> | ||||
|         {entries.map((term) => ( | ||||
|           <GlossaryEntry term={term} key={term.term} /> | ||||
|           <GlossaryEntry | ||||
|             term={term} | ||||
|             key={term.term} | ||||
|             onSelectCitation={setSelectedCitation} | ||||
|           /> | ||||
|         ))} | ||||
|       </dl> | ||||
|     </div> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
| export default App; | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
|   flex-flow: row wrap; | ||||
|   align-items: center; | ||||
|   gap: 1ch; | ||||
|   margin-block: 1rem; | ||||
|   button { | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import { useState } from "react"; | ||||
| import "./AlphabeticalFilter.css"; | ||||
|  | ||||
| type AlphabeticalFilterProps = { | ||||
| interface AlphabeticalFilterProps { | ||||
|   onChange: (value: string) => void; | ||||
| }; | ||||
| } | ||||
|  | ||||
| function AlphabeticalFilter({ onChange }: AlphabeticalFilterProps) { | ||||
|   const [selectedLetter, setSelectedLetter] = useState<string | null>(null); | ||||
| @@ -24,6 +24,7 @@ function AlphabeticalFilter({ onChange }: AlphabeticalFilterProps) { | ||||
|   const buttons = letters.map((letter) => ( | ||||
|     <button | ||||
|       onClick={() => handleClick(letter)} | ||||
|       type="button" | ||||
|       key={letter} | ||||
|       className={[ | ||||
|         "alphabetical-filter-button", | ||||
|   | ||||
							
								
								
									
										16
									
								
								src/components/CitationEntry/CitationEntry.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/components/CitationEntry/CitationEntry.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| .citation-entry { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   padding: 10px; | ||||
|   margin-bottom: 10px; | ||||
|   gap: 1rem; | ||||
|   max-width: 48ch; | ||||
|  | ||||
|   .title { | ||||
|     font-weight: bold; | ||||
|   } | ||||
|  | ||||
|   .authors { | ||||
|     font-style: italic; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										60
									
								
								src/components/CitationEntry/CitationEntry.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/components/CitationEntry/CitationEntry.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| import { useEffect, useRef } from "react"; | ||||
| import type { Citation } from "../../lib/nist-api"; | ||||
| import "./CitationEntry.css"; | ||||
| interface CitationEntryProps { | ||||
|   citation?: Citation; | ||||
|   onClose?: () => void; | ||||
| } | ||||
|  | ||||
| export default function CitationEntry({ | ||||
|   citation, | ||||
|   onClose, | ||||
| }: CitationEntryProps) { | ||||
|   const ref = useRef<HTMLDivElement>(null); | ||||
|   useEffect(() => { | ||||
|     if (!ref.current) { | ||||
|       return; | ||||
|     } | ||||
|     if (citation) { | ||||
|       ref.current.showPopover(); | ||||
|     } | ||||
|   }, [citation]); | ||||
|   if (!citation) { | ||||
|     return null; | ||||
|   } | ||||
|   const richTitle = citation.year | ||||
|     ? `${citation.title} (${citation.year})` | ||||
|     : citation.title; | ||||
|  | ||||
|   const onToggle = (e: React.ToggleEvent) => { | ||||
|     if (e.newState === "closed") { | ||||
|       onClose?.(); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <div | ||||
|       popover="auto" | ||||
|       ref={ref} | ||||
|       className="citation-modal" | ||||
|       onToggle={onToggle} | ||||
|     > | ||||
|       <div className="citation-entry"> | ||||
|         <div className="title"> | ||||
|           {citation.url ? ( | ||||
|             <a href={citation.url} target="_blank"> | ||||
|               {richTitle} | ||||
|             </a> | ||||
|           ) : ( | ||||
|             richTitle | ||||
|           )} | ||||
|         </div> | ||||
|         {citation.authors && <div className="authors">{citation.authors}</div>} | ||||
|         {citation.publication && <div>{citation.publication}</div>} | ||||
|         {citation.issue && <div>Issue {citation.issue}</div>} | ||||
|         {citation.volume && <div>Volume {citation.volume}</div>} | ||||
|         {citation.pages && <div>{citation.pages}</div>} | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										9
									
								
								src/components/ErrorBoundary/ErrorBoundary.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/components/ErrorBoundary/ErrorBoundary.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| .error-boundary { | ||||
|   color: red; | ||||
|   font-size: 1.2rem; | ||||
|   text-align: center; | ||||
|  | ||||
|   pre { | ||||
|     color: var(--color-text); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										43
									
								
								src/components/ErrorBoundary/ErrorBoundary.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/components/ErrorBoundary/ErrorBoundary.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| import type { ReactNode } from "react"; | ||||
| import React, { Component } from "react"; | ||||
|  | ||||
| import "./ErrorBoundary.css"; | ||||
|  | ||||
| interface ErrorBoundaryProps { | ||||
|   children: ReactNode; | ||||
| } | ||||
|  | ||||
| interface ErrorBoundaryState { | ||||
|   hasError: boolean; | ||||
|   error: Error | null; | ||||
| } | ||||
|  | ||||
| class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> { | ||||
|   constructor(props: ErrorBoundaryProps) { | ||||
|     super(props); | ||||
|     this.state = { hasError: false, error: null }; | ||||
|   } | ||||
|  | ||||
|   static getDerivedStateFromError(error: Error) { | ||||
|     return { hasError: true, error }; | ||||
|   } | ||||
|  | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   componentDidCatch(_: Error, __: React.ErrorInfo) { | ||||
|     // You can log the error to an error reporting service here | ||||
|   } | ||||
|  | ||||
|   render() { | ||||
|     if (this.state.hasError) { | ||||
|       return ( | ||||
|         <div className="error-boundary"> | ||||
|           <h2>Something went wrong.</h2> | ||||
|           <pre>{this.state.error?.message}</pre> | ||||
|         </div> | ||||
|       ); | ||||
|     } | ||||
|     return this.props.children; | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default ErrorBoundary; | ||||
| @@ -2,10 +2,6 @@ dt { | ||||
|   font-weight: bold; | ||||
| } | ||||
|  | ||||
| dd { | ||||
|   max-width: 64ch; | ||||
| } | ||||
|  | ||||
| dd + dd { | ||||
|   margin-top: 1rem; | ||||
|   border-top: 1px solid var(--color-border); | ||||
| @@ -19,3 +15,13 @@ dd + dt { | ||||
| dt + dd { | ||||
|   margin-top: 1rem; | ||||
| } | ||||
|  | ||||
| .citation { | ||||
|   display: block; | ||||
|   cursor: pointer; | ||||
|   font-size: 0.8em; | ||||
|   color: var(--color-text-alt); | ||||
| } | ||||
| .citation::after { | ||||
|   content: " ↗"; | ||||
| } | ||||
|   | ||||
| @@ -1,16 +1,28 @@ | ||||
| import type { GlossaryTerm } from "../../lib/nist-api"; | ||||
| import { hash } from "../../lib/util"; | ||||
| import "./GlossaryEntry.css"; | ||||
|  | ||||
| type GlossaryEntryProps = { | ||||
| interface GlossaryEntryProps { | ||||
|   term: GlossaryTerm; | ||||
| }; | ||||
|   onSelectCitation?: (citationKey: string) => void; | ||||
| } | ||||
|  | ||||
| function GlossaryEntry({ term }: GlossaryEntryProps) { | ||||
| function GlossaryEntry({ term, onSelectCitation }: GlossaryEntryProps) { | ||||
|   return ( | ||||
|     <> | ||||
|       <dt key={term.term}>{term.term}</dt> | ||||
|       {term.definitions.map((def, index) => ( | ||||
|         <dd key={term.term + index}>{def.definition}</dd> | ||||
|       {term.definitions.map((def) => ( | ||||
|         <dd key={`${hash(def.definition)}-${hash(def.citationKey)}`}> | ||||
|           {def.definition} | ||||
|           <a | ||||
|             className="citation" | ||||
|             onClick={() => | ||||
|               onSelectCitation && onSelectCitation(def.citationKey) | ||||
|             } | ||||
|           > | ||||
|             {def.citationKey} | ||||
|           </a> | ||||
|         </dd> | ||||
|       ))} | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
							
								
								
									
										4
									
								
								src/components/StringFilter/StringFilter.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/components/StringFilter/StringFilter.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| .string-filter { | ||||
|   width: 32ch; | ||||
|   margin-block: 1rem; | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| import { useState } from "react"; | ||||
| import "./StringFilter.css"; | ||||
|  | ||||
| function StringFilter({ onChange }: { onChange: (value: string) => void }) { | ||||
|   const [inputValue, setInputValue] = useState(""); | ||||
| @@ -11,6 +12,7 @@ function StringFilter({ onChange }: { onChange: (value: string) => void }) { | ||||
|  | ||||
|   return ( | ||||
|     <input | ||||
|       className="string-filter" | ||||
|       type="text" | ||||
|       value={inputValue} | ||||
|       onChange={handleChange} | ||||
|   | ||||
| @@ -1,10 +1,22 @@ | ||||
| import { useQuery } from "@tanstack/react-query"; | ||||
| import { fetchGlossary } from "../lib/nist-api"; | ||||
| import { useSuspenseQuery } from "@tanstack/react-query"; | ||||
| import { fetchCitations, fetchDefinitions } from "../lib/nist-api"; | ||||
|  | ||||
| const useGlossary = () => | ||||
|   useQuery({ | ||||
|     queryKey: ["glossary"], | ||||
|     queryFn: fetchGlossary, | ||||
|     staleTime: 5 * 60 * 1000, // 5 minutes | ||||
| const useGlossary = () => { | ||||
|   const definitionsQuery = useSuspenseQuery({ | ||||
|     queryKey: ["definitions"], | ||||
|     queryFn: fetchDefinitions, | ||||
|     staleTime: 1000 * 60 * 60, // 1 hour | ||||
|   }); | ||||
|   const citationsQuery = useSuspenseQuery({ | ||||
|     queryKey: ["citations"], | ||||
|     queryFn: fetchCitations, | ||||
|     staleTime: 1000 * 60 * 60, // 1 hour | ||||
|   }); | ||||
|  | ||||
|   return { | ||||
|     glossary: definitionsQuery.data, | ||||
|     citations: citationsQuery.data, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export default useGlossary; | ||||
|   | ||||
| @@ -25,19 +25,24 @@ a:hover { | ||||
| } | ||||
|  | ||||
| body { | ||||
|   margin: 0; | ||||
|   margin: 1rem; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   place-items: center; | ||||
|   min-width: 320px; | ||||
|   min-height: 100vh; | ||||
| } | ||||
|  | ||||
| footer { | ||||
|   margin-top: 2rem; | ||||
|   font-size: 0.6rem; | ||||
| } | ||||
|  | ||||
| #root { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   place-items: center; | ||||
|   width: 100%; | ||||
|   padding: 2rem; | ||||
|   max-width: 100%; | ||||
| } | ||||
|  | ||||
| button { | ||||
| @@ -47,7 +52,7 @@ button { | ||||
|   font-size: 1em; | ||||
|   font-weight: 500; | ||||
|   font-family: inherit; | ||||
|   background-color: #1a1a1a; | ||||
|   background-color: var(--color-background-alt); | ||||
|   cursor: pointer; | ||||
|   transition: border-color 0.25s; | ||||
| } | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| import { Axios } from "axios"; | ||||
| import Papa from "papaparse"; | ||||
|  | ||||
| export type Definition = { | ||||
| export interface Definition { | ||||
|   definition: string; | ||||
|   citationKey: string; | ||||
| }; | ||||
| } | ||||
|  | ||||
| export type Citation = { | ||||
| export interface Citation { | ||||
|   key: string; | ||||
|   title: string; | ||||
|   authors: string; | ||||
| @@ -16,37 +16,61 @@ export type Citation = { | ||||
|   pages?: string; | ||||
|   year: string; | ||||
|   url: string; | ||||
| }; | ||||
| } | ||||
|  | ||||
| export type GlossaryTerm = { | ||||
| export interface GlossaryTerm { | ||||
|   term: string; | ||||
|   definitions: Definition[]; | ||||
|   relatedTerms: string; | ||||
|   legalDefinition: string; | ||||
| }; | ||||
| } | ||||
|  | ||||
| export type Glossary = { | ||||
| export interface Glossary { | ||||
|   definitions: GlossaryTerm[]; | ||||
|   citations: Citation[]; | ||||
| }; | ||||
| } | ||||
|  | ||||
| const glossaryUrl = | ||||
| const baseUrl = | ||||
|   "https://docs.google.com/spreadsheets/d/e/2PACX-1vTRBYglcOtgaMrdF11aFxfEY3EmB31zslYI4q2_7ZZ8z_1lKm7OHtF0t4xIsckuogNZ3hRZAaDQuv_K/pub?output=csv"; | ||||
| const glossaryGid = "0"; | ||||
| const citationGid = "2053825396"; | ||||
|  | ||||
| export const fetchGlossary = async () => { | ||||
|   const resp = await new Axios().get(glossaryUrl); | ||||
|   const data = resp.data; | ||||
|   return parseGlossary(data); | ||||
| const makeClient = () => { | ||||
|   const client = new Axios({ | ||||
|     baseURL: baseUrl, | ||||
|     timeout: 2500, | ||||
|   }); | ||||
|   return client; | ||||
| }; | ||||
|  | ||||
| const parseGlossary = (data: string): Glossary => { | ||||
|   const parsed = Papa.parse<string[]>(data, { | ||||
| export const fetchCitations = async () => { | ||||
|   const client = makeClient(); | ||||
|   const resp = await client.get<string>(baseUrl, { | ||||
|     params: { | ||||
|       gid: citationGid, | ||||
|     }, | ||||
|   }); | ||||
|   return parseCitations(resp.data); | ||||
| }; | ||||
|  | ||||
| export const fetchDefinitions = async () => { | ||||
|   const client = makeClient(); | ||||
|   const resp = await client.get<string>(baseUrl, { | ||||
|     params: { | ||||
|       gid: glossaryGid, | ||||
|     }, | ||||
|   }); | ||||
|   return parseDefinitions(resp.data); | ||||
| }; | ||||
|  | ||||
| const parseDefinitions = (glossaryData: string): GlossaryTerm[] => { | ||||
|   const terms: GlossaryTerm[] = []; | ||||
|   const parsed = Papa.parse<string[]>(glossaryData, { | ||||
|     delimiter: ",", | ||||
|     header: false, | ||||
|     dynamicTyping: true, | ||||
|     skipEmptyLines: true, | ||||
|   }); | ||||
|   const glossary: Glossary = { definitions: [], citations: [] }; | ||||
|  | ||||
|   for (const record of parsed.data.slice(1)) { | ||||
|     const fields = record.map((field) => | ||||
| @@ -70,7 +94,7 @@ const parseGlossary = (data: string): Glossary => { | ||||
|     const legalDefinition = fields[fields.length - 1]; | ||||
|  | ||||
|     if (term && definitions.length > 0) { | ||||
|       glossary.definitions.push({ | ||||
|       terms.push({ | ||||
|         term, | ||||
|         definitions, | ||||
|         relatedTerms, | ||||
| @@ -78,6 +102,33 @@ const parseGlossary = (data: string): Glossary => { | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return glossary; | ||||
|   return terms; | ||||
| }; | ||||
|  | ||||
| const parseCitations = (citationData: string): Citation[] => { | ||||
|   const citations: Citation[] = []; | ||||
|   const parsed = Papa.parse<string[]>(citationData, { | ||||
|     delimiter: ",", | ||||
|     header: false, | ||||
|     dynamicTyping: true, | ||||
|     skipEmptyLines: true, | ||||
|   }); | ||||
|  | ||||
|   for (const record of parsed.data.slice(1)) { | ||||
|     const fields = record; | ||||
|  | ||||
|     const citation: Citation = { | ||||
|       key: fields[0], | ||||
|       title: fields[1], | ||||
|       authors: fields[2], | ||||
|       publication: fields[3], | ||||
|       volume: fields[4], | ||||
|       issue: fields[5], | ||||
|       pages: fields[6], | ||||
|       year: fields[7], | ||||
|       url: fields[8], | ||||
|     }; | ||||
|     citations.push(citation); | ||||
|   } | ||||
|   return citations; | ||||
| }; | ||||
|   | ||||
							
								
								
									
										13
									
								
								src/lib/util.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/lib/util.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| /** | ||||
|  * Generate a (non-cryptographic) hash value from a string. | ||||
|  * @param s string | ||||
|  * @returns string | ||||
|  */ | ||||
| export function hash(s: string): string { | ||||
|   let hash = 0; | ||||
|   for (let i = 0; i < s.length; i++) { | ||||
|     hash = (hash << 5) - hash + s.charCodeAt(i); | ||||
|     hash |= 0; // Convert to 32bit integer | ||||
|   } | ||||
|   return hash.toString(16); | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/main.tsx
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								src/main.tsx
									
									
									
									
									
								
							| @@ -1,15 +1,26 @@ | ||||
| import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; | ||||
| import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; | ||||
| import { StrictMode } from "react"; | ||||
| import { StrictMode, Suspense } from "react"; | ||||
| import { createRoot } from "react-dom/client"; | ||||
| import App from "./App.tsx"; | ||||
| import ErrorBoundary from "./components/ErrorBoundary/ErrorBoundary"; | ||||
| import "./index.css"; | ||||
| const queryClient = new QueryClient(); | ||||
|  | ||||
| createRoot(document.getElementById("root")!).render( | ||||
|   <StrictMode> | ||||
|     <QueryClientProvider client={queryClient}> | ||||
|       <App /> | ||||
|       <ErrorBoundary> | ||||
|         <Suspense | ||||
|           fallback={ | ||||
|             <div style={{ textAlign: "center", marginTop: "2rem" }}> | ||||
|               Loading... | ||||
|             </div> | ||||
|           } | ||||
|         > | ||||
|           <App /> | ||||
|         </Suspense> | ||||
|       </ErrorBoundary> | ||||
|       <ReactQueryDevtools initialIsOpen={false} /> | ||||
|     </QueryClientProvider> | ||||
|   </StrictMode> | ||||
|   | ||||
| @@ -2,7 +2,9 @@ | ||||
|   color: rgba(255, 255, 255, 0.87); | ||||
|  | ||||
|   --color-background: #242424; | ||||
|   --color-background-alt: #1e1e1e; | ||||
|   --color-text: #ffffff; | ||||
|   --color-text-alt: #aaaaaa; | ||||
|   --color-accent: #646cff; | ||||
|   --color-accent-hover: #535bf2; | ||||
|   --color-border: #444444; | ||||
| @@ -11,7 +13,9 @@ | ||||
| @media (prefers-color-scheme: light) { | ||||
|   :root { | ||||
|     --color-background: #ffffff; | ||||
|     --color-background-alt: #f5f5f5; | ||||
|     --color-text: #213547; | ||||
|     --color-text-alt: #000000; | ||||
|     --color-accent: #747bff; | ||||
|     --color-accent-hover: #646cff; | ||||
|     --color-border: #d9d9d9; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { defineConfig } from 'vite' | ||||
| import react from '@vitejs/plugin-react' | ||||
| import react from "@vitejs/plugin-react-swc"; | ||||
| import { defineConfig } from "vite"; | ||||
|  | ||||
| // https://vite.dev/config/ | ||||
| export default defineConfig({ | ||||
|   plugins: [react()], | ||||
| }) | ||||
| }); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user