diff --git a/src/assets/data/publications.json b/src/assets/data/publications.json new file mode 100644 index 0000000..a44b390 --- /dev/null +++ b/src/assets/data/publications.json @@ -0,0 +1,498 @@ +{ + "publications": [ + { + "id": 1, + "category": "Journal Articles", + "type": "journal-article", + "year": 2024, + "content": "Davidson O., Lee M.L., Kam J.P., Brush M., Rajesh A., Blazes M., Arterburn D., Duerr E., Gibbons L.E., Crane P.K., Lee C.S., Eye ACT Study Group. (2024). Associations between Dementia and Exposure to Topical Glaucoma Medications. *J Alzheimer’s Dis.* Accepted for publication.", + "authors": [ + "Davidson O.", + "Lee M.L.", + "Kam J.P.", + "Brush M.", + "Rajesh A.", + "Blazes M.", + "Arterburn D.", + "Duerr E.", + "Gibbons L.E.", + "Crane P.K.", + "Lee C.S.", + "Eye ACT Study Group" + ], + "journal": "J Alzheimer’s Dis.", + "status": "published", + "tags": [] + }, + { + "id": 2, + "category": "Journal Articles", + "type": "journal-article", + "year": 2024, + "content": "Lee C.S., Ferguson A.N., Gibbons L.E., Walker R., Su Y.R., Krakauer C., Brush M., Kam J., Larson E.B., Arterburn D.E., Crane P.K., Eye ACT Study Group. (2024). Eye Adult Changes in Thought (Eye ACT) Study: Design and Report on the Inaugural Cohort. *J Alzheimers Dis.* 100(1):309-320. . PMID: 38875039.", + "authors": [ + "Lee C.S.", + "Ferguson A.N.", + "Gibbons L.E.", + "Walker R.", + "Su Y.R.", + "Krakauer C.", + "Brush M.", + "Kam J.", + "Larson E.B.", + "Arterburn D.E.", + "Crane P.K.", + "Eye ACT Study Group" + ], + "journal": "J Alzheimers Dis.", + "volume": 100, + "issue": 1, + "pages": "309-320", + "doi": "10.3233/JAD-240203", + "pmid": 38875039, + "status": "published", + "tags": [] + }, + { + "id": 3, + "category": "Journal Articles", + "type": "journal-article", + "year": 2024, + "content": "Gibbons L.E., Mobley T., Mayeda E.R., Lee C.S., Gatto N.M., LaCroix A.Z., McEvoy L.K., Crane P.K., Hayes-Larson E. (2024). How Generalizable Are Findings from a Community-Based Prospective Cohort Study? *J Alzheimers Dis.* 100(1):163-174. . PMID: 38848188; PMCID: PMC11423796.", + "authors": [ + "Gibbons L.E.", + "Mobley T.", + "Mayeda E.R.", + "Lee C.S.", + "Gatto N.M.", + "LaCroix A.Z.", + "McEvoy L.K.", + "Crane P.K.", + "Hayes-Larson E." + ], + "journal": "J Alzheimers Dis.", + "volume": 100, + "issue": 1, + "pages": "163-174", + "doi": "10.3233/JAD-240247", + "pmid": 38848188, + "pmcid": "PMC11423796", + "status": "published", + "tags": [] + }, + { + "id": 4, + "category": "Journal Articles", + "type": "journal-article", + "year": 2023, + "content": "Lee C.S., Krakauer C., Su Y.R., Walker R.L., Blazes M., McCurry S.M., Bowen J.D., McCormick W.C., Lee A.Y., Boyko E.J., O'Hare A.M., Larson E.B., Crane P.K. (2023). Diabetic Retinopathy and Dementia Association, Beyond Diabetes Severity. *Am J Ophthalmol.* 249:90-98. . PMID: 36513155; PMCID: PMC10106379.", + "authors": [ + "Lee C.S.", + "Krakauer C.", + "Su Y.R.", + "Walker R.L.", + "Blazes M.", + "McCurry S.M.", + "Bowen J.D.", + "McCormick W.C.", + "Lee A.Y.", + "Boyko E.J.", + "O'Hare A.M.", + "Larson E.B.", + "Crane P.K." + ], + "journal": "Am J Ophthalm", + "volume": 249, + "pages": "90-98", + "doi": "10.1016/j.ajo.2022.12.003", + "pmid": 36513155, + "pmcid": "PMC10106379", + "status": "published", + "tags": [] + }, + { + "id": 5, + "category": "Journal Articles", + "type": "journal-article", + "year": 2023, + "content": "Tang M.Y., Blazes M.S., Lee C.S. (2023). Imaging Amyloid and Tau in the Retina: Current Research and Future Directions. *J Neuroophthalmol.* 43(2):168-179. . PMID: 36705970; PMCID: PMC10191872.", + "authors": ["Tang M.Y.", "Blazes M.S.", "Lee C.S."], + "journal": "J Neuroophthalmol.", + "volume": 43, + "issue": 2, + "pages": "168-179", + "doi": "10.1097/WNO.0000000000001786", + "pmid": 36705970, + "pmcid": "PMC10191872", + "status": "published", + "tags": [] + }, + { + "id": 6, + "category": "Journal Articles", + "type": "journal-article", + "year": 2022, + "content": "Yuan A., Lee C.S. (2022). Retinal Biomarkers for Alzheimer Disease: The Facts and the Future. *Asia Pac J Ophthalmol (Phila).* 11(2):140-148. . PMID: 35533333; PMCID: PMC9889204.", + "authors": ["Yuan A.", "Lee C.S."], + "journal": "Asia Pac J Ophthalmol (Phila).", + "volume": 11, + "issue": 2, + "pages": "140-148", + "doi": "10.1097/APO.0000000000000505", + "pmid": 35533333, + "pmcid": "PMC9889204", + "status": "published", + "tags": [] + }, + { + "id": 7, + "category": "Journal Articles", + "type": "journal-article", + "year": 2021, + "content": "Lee C.S., Gibbons L.E., Lee A.Y., Yanagihara R.T., Blazes M.S., Lee M.L., McCurry S.M., Bowen J.D., McCormick W.C., Crane P.K., Larson E.B. (2021). Association Between Cataract Extraction and Development of Dementia. *JAMA Intern Med.* . PMID: 34870676; PMCID: PMC8649913.", + "authors": [ + "Lee C.S.", + "Gibbons L.E.", + "Lee A.Y.", + "Yanagihara R.T.", + "Blazes M.S.", + "Lee M.L.", + "McCurry S.M.", + "Bowen J.D.", + "McCormick W.C.", + "Crane P.K.", + "Larson E.B." + ], + "journal": "JAMA Intern Med.", + "doi": "https://doi.org/10.1001/jamainternmed.2021.6990", + "pmid": 34870676, + "pmcid": "PMC8649913", + "status": "published", + "tags": [] + }, + { + "id": 8, + "category": "Journal Articles", + "type": "journal-article", + "year": 2021, + "content": "Lee C.S., Latimer C.S., Henriksen J.C., Blazes M., Larson E.B., Crane P.K., Keene C.D., Lee A.Y. (2021). Application of deep learning to understand resilience to Alzheimer's disease pathology. *Brain Pathol.* 31(6):e12974. . PMID: 34009663; PMCID: PMC8549025.", + "authors": [ + "Lee C.S.", + "Latimer C.S.", + "Henriksen J.C.", + "Blazes M.", + "Larson E.B.", + "Crane P.K.", + "Keene C.D.", + "Lee A.Y." + ], + "journal": "Brain Pathol.", + "volume": 31, + "issue": 6, + "pages": "e12974", + "doi": "10.1111/bpa.12974", + "pmid": 34009663, + "pmcid": "PMC8549025", + "status": "published", + "tags": [] + }, + { + "id": 9, + "category": "Journal Articles", + "type": "journal-article", + "year": 2021, + "content": "Lee C.S., Lee M.L., Gibbons L.E., Yanagihara R.T., Blazes M., Kam J.P., McCurry S.M., Bowen J.D., McCormick W.C., Lee A.Y., Larson E.B., Crane P.K. (2021). Associations Between Retinal Artery/Vein Occlusions and Risk of Vascular Dementia. *J Alzheimers Dis.* 81(1):245-253. . PMID: 33749651; PMCID: PMC8168611.", + "authors": [ + "Lee C.S.", + "Lee M.L.", + "Gibbons L.E.", + "Yanagihara R.T.", + "Blazes M.", + "Kam J.P.", + "McCurry S.M.", + "Bowen J.D.", + "McCormick W.C.", + "Lee A.Y.", + "Larson E.B.", + "Crane P.K." + ], + "journal": "J Alzheimers Dis.", + "volume": 81, + "issue": 1, + "pages": "245-253", + "doi": "https://doi.org/10.3233/JAD-201492", + "pmid": 33749651, + "pmcid": "PMC8168611", + "status": "published", + "tags": [] + }, + { + "id": 10, + "category": "Journal Articles", + "type": "journal-article", + "year": 2021, + "content": "Blazes M., Lee C.S. (2021). Understanding the Brain through Aging Eyes. *Adv Geriatr Med Res.* 3(2):e210008. . PMID: 33748826; PMCID: PMC7971450.", + "authors": ["Blazes M.", "Lee C.S."], + "journal": "Adv Geriatr Med Res.", + "volume": 3, + "issue": 2, + "pages": "e210008", + "doi": "https://doi.org/10.20900/agmr20210008", + "pmid": 33748826, + "pmcid": "PMC7971450", + "status": "published", + "tags": [] + }, + { + "id": 11, + "category": "Journal Articles", + "type": "journal-article", + "year": 2019, + "content": "Lee C.S., Larson E.B., Gibbons L.E., Latimer C.S., Rose S.E., Hellstern L.L., Keene C.D., Crane P.K.; Adult Changes in Thought (ACT) Study. (2019). Ophthalmology-Based Neuropathology Risk Factors: Diabetic Retinopathy is Associated with Deep Microinfarcts in a Community-Based Autopsy Study. *J Alzheimers Dis.* 68(2):647-655. . PMID: 30883356; PMCID: PMC6450649.", + "authors": [ + "Lee C.S.", + "Larson E.B.", + "Gibbons L.E.", + "Latimer C.S.", + "Rose S.E.", + "Hellstern L.L.", + "Keene C.D.", + "Crane P.K.", + "Adult Changes in Thought (ACT) Study" + ], + "journal": "J Alzheimers Dis.", + "volume": 68, + "issue": 2, + "pages": "647-655", + "doi": "https://doi.org/10.3233/JAD-181087", + "pmid": 30883356, + "pmcid": "PMC6450649", + "status": "published", + "tags": [] + }, + { + "id": 12, + "category": "Abstracts", + "type": "abstract", + "year": 2024, + "content": "Zhao K., Duong C., Ngadisastra C., Takahashi M., Pope B., Schaaf B., Cooper J., Kam J., Brush M., Gibbons L., Lee A.Y., Arterburn D., Larson E., Crane P.K., Lee C.S. (2024). Eye Adult Changes in Thought (Eye ACT) study: Settings and report on the inaugural cohort. *Invest. Ophthalmol. Vis. Sci.* 65(7):6367.", + "authors": [ + "Zhao K.", + "Duong C.", + "Ngadisastra C.", + "Takahashi M.", + "Pope B.", + "Schaaf B.", + "Cooper J.", + "Kam J.", + "Brush M.", + "Gibbons L.", + "Lee A.Y.", + "Arterburn D.", + "Larson E.", + "Crane P.K.", + "Lee C.S." + ], + "journal": "Invest. Ophthalmol. Vis. Sci.", + "volume": 65, + "issue": 7, + "pages": "6367", + "status": "published", + "tags": [] + }, + { + "id": 13, + "category": "Abstracts", + "type": "abstract", + "year": 2023, + "content": "Duong C., Davidson O., Hong Y., Pope B., Kam J., Brush M., Lacy M., Cooper J., Takahashi M., Larson E., Arterburn D., Crane P., Lee A., Lee C.S. (2023). Analysis of prospective data from the Eye ACT study. *Invest. Ophthalmol. Vis. Sci.* 64(8):4242.", + "authors": [ + "Duong C.", + "Davidson O.", + "Hong Y.", + "Pope B.", + "Kam J.", + "Brush M.", + "Lacy M.", + "Cooper J.", + "Takahashi M.", + "Larson E.", + "Arterburn D.", + "Crane P.", + "Lee A.", + "Lee C.S." + ], + "journal": "Invest. Ophthalmol. Vis. Sci.", + "volume": 64, + "issue": 8, + "pages": "4242", + "status": "published", + "tags": [] + }, + { + "id": 14, + "category": "Abstracts", + "type": "abstract", + "year": 2023, + "content": "Davidson O., Lee M., Gibbons L., Duerr E., Kam J., Brush M., Lee A.Y., Crane P., Lee C.S. (2023). Associations Between Dementia Risks and Chronic Exposures to Different Glaucoma Medication Types. *Invest. Ophthalmol. Vis. Sci.* 64(8):128.", + "authors": [ + "Davidson O.", + "Lee M.", + "Gibbons L.", + "Duerr E.", + "Kam J.", + "Brush M.", + "Lee A.Y.", + "Crane P.", + "Lee C.S." + ], + "journal": "Invest. Ophthalmol. Vis. Sci.", + "volume": 64, + "issue": 8, + "pages": "128", + "status": "published", + "tags": [] + }, + { + "id": 15, + "category": "Abstracts", + "type": "abstract", + "year": 2021, + "content": "Blazes M., Lee M.L., Gibbons L.E., Yanagihara R.T., Kam J.P., Lee A.Y., Larson E.B., Crane P.K., Lee C.S. (2021). Associations between retinal artery/vein occlusions (RAVO) and risk of vascular dementia. *Invest. Ophthalmol. Vis. Sci.* 62(8):2830.", + "authors": [ + "Blazes M.", + "Lee M.L.", + "Gibbons L.E.", + "Yanagihara R.T.", + "Kam J.P.", + "Lee A.Y.", + "Larson E.B.", + "Crane P.K.", + "Lee C.S." + ], + "journal": "Invest. Ophthalmol. Vis. Sci.", + "volume": 62, + "issue": 8, + "pages": "2830", + "status": "published", + "tags": [] + }, + { + "id": 16, + "category": "Abstracts", + "type": "abstract", + "year": 2020, + "content": "Lee C.S., Lee M.L., Gibbons L.E., et al. (2020). Retinal vascular occlusions are associated with increased risk for vascular dementia in APOE ε4 positive group in a community-based cohort. *Alzheimer Association International Conference (AAIC)*, July 2020.", + "authors": ["Lee C.S.", "Lee M.L.", "Gibbons L.E."], + "conference": "Alzheimer Association International Conference (AAIC)", + "date": "July 2020", + "status": "published", + "tags": [] + }, + { + "id": 17, + "category": "Lectures/Presentations", + "type": "presentation", + "year": 2023, + "date": "June 2023", + "content": "**June 2023**: Vickie and Jack Farber Vision Research Center Lecture, Will’s Eye Hospital, Philadelphia, PA: *Connecting the Dots between the Eye and the Brain*.", + "event": "Vickie and Jack Farber Vision Research Center Lecture", + "location": "Will’s Eye Hospital, Philadelphia, PA", + "title": "Connecting the Dots between the Eye and the Brain", + "tags": [] + }, + { + "id": 18, + "category": "Lectures/Presentations", + "type": "presentation", + "year": 2023, + "date": "May 2023", + "content": "**May 2023**: Adult Changes in Thought (ACT) symposium, Seattle, WA: *Sensory Impairment and Dementia Development*.", + "event": "Adult Changes in Thought (ACT) symposium", + "location": "Seattle, WA", + "title": "Sensory Impairment and Dementia Development", + "tags": [] + }, + { + "id": 19, + "category": "Lectures/Presentations", + "type": "presentation", + "year": 2023, + "date": "January 2023", + "content": "**January 2023**: FDA Collaborative Community on Ophthalmic Imaging (CCOI), Virtual: *Retinal Imaging and Dementia*.", + "event": "FDA Collaborative Community on Ophthalmic Imaging (CCOI)", + "location": "Virtual", + "title": "Retinal Imaging and Dementia", + "tags": [] + }, + { + "id": 20, + "category": "Lectures/Presentations", + "type": "presentation", + "year": 2022, + "date": "November 2022", + "content": "**November 2022**: Alzheimer’s Drug Discovery Foundation, Diagnostic Accelerator, Investigators’ Meeting: *Exploring Ophthalmic Biomarkers of Alzheimer’s Disease*.", + "event": "Alzheimer’s Drug Discovery Foundation, Diagnostic Accelerator, Investigators’ Meeting", + "title": "Exploring Ophthalmic Biomarkers of Alzheimer’s Disease", + "tags": [] + }, + { + "id": 21, + "category": "Lectures/Presentations", + "type": "presentation", + "year": 2022, + "date": "October 2022", + "content": "**October 2022**: NHLBI Retinal Workshop, Bethesda, MD: *Eye and Dementia*.", + "event": "NHLBI Retinal Workshop", + "location": "Bethesda, MD", + "title": "Eye and Dementia", + "tags": [] + }, + { + "id": 22, + "category": "Lectures/Presentations", + "type": "presentation", + "year": 2022, + "date": "March 2022", + "content": "**March 2022**: Society for Brain Mapping & Therapeutics (SBMT), Los Angeles, CA: *Connecting the Dots between Aging Eyes and the Brain*.", + "event": "Society for Brain Mapping & Therapeutics (SBMT)", + "location": "Los Angeles, CA", + "title": "Connecting the Dots between Aging Eyes and the Brain", + "tags": [] + }, + { + "id": 23, + "category": "Lectures/Presentations", + "type": "presentation", + "year": 2021, + "date": "May 2021", + "content": "**May 2021**: Association for Research in Vision and Ophthalmology (ARVO) Annual Meeting: *Diabetic Retinopathy and Dementia Link is More Than Microvascular Disease and Poor Glycemic Control Mechanisms*.", + "event": "Association for Research in Vision and Ophthalmology (ARVO) Annual Meeting", + "title": "Diabetic Retinopathy and Dementia Link is More Than Microvascular Disease and Poor Glycemic Control Mechanisms", + "tags": [] + }, + { + "id": 24, + "category": "Lectures/Presentations", + "type": "presentation", + "year": 2020, + "date": "October 2020", + "content": "**October 2020**: Alcon Research Institute (ARI) Podos Colloquium: *Studying the Aging Brain through the Aging Eyes*.", + "event": "Alcon Research Institute (ARI) Podos Colloquium", + "title": "Studying the Aging Brain through the Aging Eyes", + "tags": [] + }, + { + "id": 25, + "category": "Lectures/Presentations", + "type": "presentation", + "year": 2020, + "date": "July 2020", + "content": "**July 2020**: Alzheimer Association International Conference (AAIC), Virtual: *Cataract Surgery is Associated with Reduced Risk for Alzheimer’s Disease*.", + "event": "Alzheimer Association International Conference (AAIC)", + "location": "Virtual", + "title": "Cataract Surgery is Associated with Reduced Risk for Alzheimer’s Disease", + "tags": [] + } + ] +} diff --git a/src/components/buttons/Button.tsx b/src/components/buttons/Button.tsx index 8deae90..bc48964 100644 --- a/src/components/buttons/Button.tsx +++ b/src/components/buttons/Button.tsx @@ -96,7 +96,9 @@ const Button = React.forwardRef( }, )} > - + {React.createElement(ImSpinner2 as React.ElementType, { + className: 'animate-spin', + })} )} {children} diff --git a/src/lib/markdownToHtml.ts b/src/lib/markdownToHtml.ts index 8e2d1bd..3735611 100644 --- a/src/lib/markdownToHtml.ts +++ b/src/lib/markdownToHtml.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import rehypeDocument from 'rehype-document'; import rehypeExternalLinks from 'rehype-external-links'; import rehypeFormat from 'rehype-format'; @@ -10,24 +11,33 @@ import remarkRehype from 'remark-rehype'; import { unified } from 'unified'; /** - * Custom plugin to wrap sections in cards + * Helper function to escape RegExp special characters. */ -function rehypeWrapInCards(params: { card: boolean }) { +const escapeRegExp = (str: string) => { + return str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); +}; + +/** + * Custom plugin to wrap sections in Tailwind cards. + * (Currently not used but can be enabled by setting the card parameter to true.) + * Wraps every section starting with an

into a card container + * with classes: + * "bg-white rounded-lg shadow-lg px-6 mb-6 pb-4 pt-1 border-gray-200 border-[1px]" + */ +const rehypeWrapInCardsPlugin = (params: { card: boolean }) => { return (tree: any) => { - const children = tree.children; + const { children } = tree; const wrappedChildren: any[] = []; let currentCard: any = null; for (let i = 0; i < children.length; i++) { const node = children[i]; - if (node.type === 'element' && node.tagName === 'h2') { // Close the current card if it exists if (currentCard) { wrappedChildren.push(currentCard); } - - // Start a new card + // Create a new card with the current section currentCard = { type: 'element', tagName: 'div', @@ -52,34 +62,158 @@ function rehypeWrapInCards(params: { card: boolean }) { // Add content to the current card currentCard.children.push(node); } else { - // Push content outside cards (e.g.,

) + // Push content outside card (e.g.,

) wrappedChildren.push(node); } } - // Push the last card if it exists if (currentCard) { wrappedChildren.push(currentCard); } - // Replace the original children with the wrapped ones tree.children = wrappedChildren; }; -} +}; + +/** + * Custom plugin to add Tailwind link classes. + * + * Adds classes "text-[#025988] hover:text-[#0e8cd0]" to every tag. + */ +const rehypeAddLinkClasses = () => { + return (tree: any) => { + const visit = (node: any) => { + if (node.type === 'element' && node.tagName === 'a') { + if (!node.properties) { + node.properties = {}; + } + const existing = node.properties.className || []; + let classes: string[] = []; + if (typeof existing === 'string') { + classes = existing.split(' '); + } else if (Array.isArray(existing)) { + classes = existing; + } + node.properties.className = [ + ...classes, + 'text-sky-500', + 'hover:text-sky-700', + ]; + } + if (node.children && Array.isArray(node.children)) { + node.children.forEach(visit); + } + }; + visit(tree); + }; +}; + +/** + * Custom plugin to highlight text nodes matching a search term. + * + * Traverses the tree and replaces text nodes that contain the search term + * with fragments that wrap matching parts in a with class "bg-yellow-200". + */ +const rehypeHighlightText = (options: { highlight: string }) => { + return (tree: any) => { + const { highlight } = options; + if (!highlight || highlight.trim() === '') { + return; + } + // Create a regular expression to match the search term + const regex = new RegExp(`(${escapeRegExp(highlight.trim())})`, 'gi'); + + /** + * Recursively traverses an HTML node and its children to highlight text. + * + * @param {any} node - A node from the HTML to process. + * @returns {any} The modified node with highlighted text fragments, or an array of nodes if the text node was split. + */ + // sourcery skip: avoid-function-declarations-in-blocks + function visit(node: any): any { + if (node.type === 'text') { + const value: string = node.value; + // Check if the text node contains a match to the search term + if (regex.test(value)) { + // Split the text node into fragments based on the search term + const fragments: any[] = []; + let lastIndex = 0; + value.replace(regex, (match, _p1, offset) => { + if (offset > lastIndex) { + fragments.push({ + type: 'text', + value: value.slice(lastIndex, offset), + }); + } + // Highlight the matched text with a span element + fragments.push({ + type: 'element', + tagName: 'span', + properties: { className: ['bg-yellow-200'] }, + children: [{ type: 'text', value: match }], + }); + lastIndex = offset + match.length; + return match; + }); + // Add the remaining text after the last match + if (lastIndex < value.length) { + fragments.push({ + type: 'text', + value: value.slice(lastIndex), + }); + } + return fragments; + } + } + // Recursively process children + if (node.children && Array.isArray(node.children)) { + const newChildren = []; + for (const child of node.children) { + const replaced = visit(child); + if (Array.isArray(replaced)) { + // If the text node was split, add the fragments to the new children + newChildren.push(...replaced); + } else if (replaced) { + // Otherwise, add the modified child + newChildren.push(replaced); + } else { + // If the child was not modified, add it as is + newChildren.push(child); + } + } + // Update the children with the modified nodes + node.children = newChildren; + } + return node; + } + visit(tree); + }; +}; /** - * Converts markdown to HTML with Tailwind cards + * Converts markdown to HTML with: + * - Tailwind card wrapping (optional) + * - Custom link styling (text-[#025988] hover:text-[#0e8cd0]) + * - Highlighting text nodes based on a search term (optional) + * + * @param markdown The markdown string to convert. + * @param card Whether to wrap sections in cards. (Default: false) + * @param highlight (Optional) The search term to highlight. */ -export default async function markdownToHtml(markdown: string, card = false) { +export default async function markdownToHtml( + markdown: string, + card = false, + highlight = '', +) { const result = await unified() .use(remarkParse) .use(remarkGfm) .use(remarkRehype, { allowDangerousHtml: true }) .use(rehypeRaw) .use(rehypeHighlight) - .use(rehypeWrapInCards, { - card, - }) // Apply card wrapping + .use(rehypeWrapInCardsPlugin, { card }) + .use(rehypeAddLinkClasses) + .use(rehypeHighlightText, { highlight }) .use(rehypeDocument, { meta: [ { name: 'viewport', content: 'width=device-width, initial-scale=1' }, diff --git a/src/pages/publications/index.tsx b/src/pages/publications/index.tsx index 8873c55..166a7a6 100644 --- a/src/pages/publications/index.tsx +++ b/src/pages/publications/index.tsx @@ -1,28 +1,275 @@ +import { CheckIcon } from '@chakra-ui/icons'; +import { + Box, + Button, + Flex, + Input, + Menu, + MenuButton, + MenuGroup, + MenuItem, + MenuList, + Tag, + TagCloseButton, + TagLabel, +} from '@chakra-ui/react'; import { SkipNavContent, SkipNavLink } from '@chakra-ui/skip-nav'; -import fs from 'fs'; -import { GetStaticProps } from 'next'; +import { Icon } from '@iconify/react'; +import Link from 'next/link'; +import { useEffect, useMemo, useState } from 'react'; import markdownToHtml from '@/lib/markdownToHtml'; import Layout from '@/components/layout/Layout'; import Seo from '@/components/Seo'; -const PublicationsPage: React.FC<{ pageContent: string }> = ({ - pageContent, -}) => { +import publicationsData from '@/assets/data/publications.json'; + +const publicationCategories = [ + 'Journal Articles', + 'Abstracts', + 'Lectures/Presentations', +]; + +/** + * Helper component that converts markdown into HTML using your custom renderer. + * Also helps prevent XSS attacks by sanitizing the HTML output + */ +const MarkdownContent: React.FC<{ + markdown: string; + card?: boolean; + highlight?: string; +}> = ({ markdown, card = false, highlight = '' }) => { + const [html, setHtml] = useState(''); + useEffect(() => { + // sourcery skip: avoid-function-declarations-in-blocks + async function processMarkdown() { + const result = await markdownToHtml(markdown, card, highlight); + setHtml(result); + } + processMarkdown(); + }, [markdown, card, highlight]); + return
; +}; + +const PublicationsPage: React.FC = () => { + // Search state + const [searchTerm, setSearchTerm] = useState(''); + + // Multi-select filter state for categories and years + const [selectedCategories, setSelectedCategories] = useState([]); + const [selectedYears, setSelectedYears] = useState([]); + + // Toggle category selection + const toggleCategory = (cat: string) => { + if (selectedCategories.includes(cat)) { + // Remove the category from the selected list + setSelectedCategories(selectedCategories.filter((c) => c !== cat)); + } else { + // Add the category to the selected list + setSelectedCategories([...selectedCategories, cat]); + } + }; + + // Toggle year selection + const toggleYear = (year: number) => { + if (selectedYears.includes(year)) { + // Remove the year from the selected list + setSelectedYears(selectedYears.filter((y) => y !== year)); + } else { + // Add the year to the selected list + setSelectedYears([...selectedYears, year]); + } + }; + + // Compute available years from the JSON data (sorted descending) + const availableYears = useMemo(() => { + const yearsSet = new Set(); + publicationsData.publications.forEach((pub) => { + if (pub.year) { + yearsSet.add(pub.year); + } + }); + return Array.from(yearsSet).sort((a, b) => b - a); + }, []); + + // Filter publications based on search term, selected categories, and years + const filteredPublications = useMemo(() => { + return publicationsData.publications.filter((pub) => { + const matchesSearch = pub.content + .toLowerCase() + .includes(searchTerm.toLowerCase()); + const matchesCategory = + selectedCategories.length === 0 || + selectedCategories.includes(pub.category); + const matchesYear = + selectedYears.length === 0 || selectedYears.includes(pub.year); + return matchesSearch && matchesCategory && matchesYear; + }); + }, [searchTerm, selectedCategories, selectedYears]); + + // Group the filtered publications by category + const groupedPublications = useMemo(() => { + return filteredPublications.reduce( + (acc, pub) => { + if (!acc[pub.category]) { + acc[pub.category] = []; + } + acc[pub.category].push(pub); + return acc; + }, + {} as Record, + ); + }, [filteredPublications]); + return ( <> Skip to content - -
+
+

Publications

+

+ We provide below a list of publications and other outcomes from + the Eye ACT project. You can also find our publications on + PubMed{' '} + + here + + . +

+
+ + {/* Filtering & Search UI */} +
+ + setSearchTerm(e.target.value)} + size='md' + className='mr-4' + /> + + +
+ Filters + {' '} +
+
+ + {/* Categories Multi-select */} + + {publicationCategories.map((cat) => ( + toggleCategory(cat)} + icon={ + selectedCategories.includes(cat) ? ( + + ) : undefined + } + > + {cat} + + ))} + + {/* Year Submenu */} + + + {/* Nested Menu for Year */} + + + Select + + + {availableYears.map((year) => ( + toggleYear(year)} + icon={ + selectedYears.includes(year) ? ( + + ) : undefined + } + > + {year} + + ))} + + + + + +
+
+ {/* Active Filter Tags */} + + {selectedCategories.map((cat) => ( + + {cat} + toggleCategory(cat)} /> + + ))} + {selectedYears.map((year) => ( + + {year} + toggleYear(year)} /> + + ))} + +
-
-
+ {/* Render the publications */} +
+ {Object.keys(groupedPublications).map((category) => ( +
+

{category}

+
    + {groupedPublications[category].map((pub) => ( +
  • + +
  • + ))} +
+
+ ))}
@@ -31,19 +278,4 @@ const PublicationsPage: React.FC<{ pageContent: string }> = ({ ); }; -export const getStaticProps: GetStaticProps = async () => { - const fileContent = fs.readFileSync( - `src/pages/publications/content.md`, - `utf-8`, - ); - - const pageContent = await markdownToHtml(fileContent || ``, true); - - return { - props: { - pageContent, - }, - }; -}; - export default PublicationsPage;