Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions theme/app/components/ArticlePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import type { PageLoader } from '@myst-theme/common';
import { Admonition, AdmonitionKind } from 'myst-to-react';
import { copyNode, type GenericParent } from 'myst-common';
import { FrontmatterBlock } from '@myst-theme/frontmatter';
import { FrontmatterBlock } from './ForntmatterBlock';
import type { SiteAction } from 'myst-config';

/**
Expand Down Expand Up @@ -57,7 +57,7 @@ export function ArticlePage({
<FrontmatterBlock
kind={article.kind}
frontmatter={{ ...article.frontmatter, ...manifest, downloads }}
className="mb-8 pt-9"
className="pt-9 mb-8"
authorStyle="list"
/>
)}
Expand Down
280 changes: 280 additions & 0 deletions theme/app/components/ForntmatterBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
import React from 'react';
import classNames from 'classnames';
import type { ExpandedThebeFrontmatter, PageFrontmatter } from 'myst-frontmatter';
import { AuthorsList, AuthorAndAffiliations } from '@myst-theme/frontmatter';
import { SourceFileKind } from 'myst-spec-ext';
import { JupyterIcon, OpenAccessIcon, GithubIcon } from '@scienceicons/react/24/solid';

function ExternalOrInternalLink({
to,
className,
title,
children,
}: {
to: string;
className?: string;
title?: string;
children: React.ReactNode;
}) {
return (
<a href={to} className={className} title={title}>
{children}
</a>
);
}

export function DoiText({ doi: possibleLink, className }: { doi?: string; className?: string }) {
if (!possibleLink) return null;
const doi = possibleLink.replace(/^(https?:\/\/)?(dx\.)?doi\.org\//, '');
const url = `https://doi.org/${doi}`;
return (
<a
className={classNames('no-underline text-inherit hover:text-inherit', className)}
target="_blank"
rel="noopener noreferrer"
href={url}
title="DOI (Digital Object Identifier)"
>
{url}
</a>
);
}

export function DoiBadge({ doi: possibleLink, className }: { doi?: string; className?: string }) {
if (!possibleLink) return null;
const doi = possibleLink.replace(/^(https?:\/\/)?(dx\.)?doi\.org\//, '');
const url = `https://doi.org/${doi}`;
return (
<div className={classNames('flex-none', className)} title="DOI (Digital Object Identifier)">
<a
className="font-light no-underline hover:font-light hover:underline text-inherit hover:text-inherit"
target="_blank"
rel="noopener noreferrer"
href={url}
>
{url}
</a>
</div>
);
}

export function DateString({
date,
format = {
year: 'numeric',
month: 'long',
day: 'numeric',
},
spacer,
}: {
date?: string;
format?: Intl.DateTimeFormatOptions;
spacer?: boolean;
}) {
if (!date) return null;
// Parse the date
// As this is a YYYY-MM-DD form, the parser interprets this as a UTC date
// (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#the_epoch_timestamps_and_invalid_date)
const utcDate = new Date(date);

// Now cast our UTC-date into the local timezone
const localDate = new Date(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), utcDate.getUTCDate());

// Then format as human-readable in the local timezone.
const dateString = localDate.toLocaleDateString('en-US', format);
return (
<time dateTime={date} className={classNames({ 'text-spacer': spacer })}>
{dateString}
</time>
);
}

export function GitHubLink({ github: possibleLink }: { github?: string }) {
if (!possibleLink) return null;
const github = possibleLink.replace(/^(https?:\/\/)?github\.com\//, '');
return (
<a
href={`https://github.com/${github}`}
title={`GitHub Repository: ${github}`}
target="_blank"
rel="noopener noreferrer"
className="text-inherit hover:text-inherit"
>
<GithubIcon
width="1.25rem"
height="1.25rem"
className="inline-block mr-1 opacity-60 hover:opacity-100"
/>
</a>
);
}

export function OpenAccessBadge({ open_access }: { open_access?: boolean }) {
if (!open_access) return null;
return (
<a
href="https://en.wikipedia.org/wiki/Open_access"
target="_blank"
rel="noopener noreferrer"
title="Open Access"
className="text-inherit hover:text-inherit"
>
<OpenAccessIcon
width="1.25rem"
height="1.25rem"
className="mr-1 inline-block opacity-60 hover:opacity-100 hover:text-[#E18435]"
/>
</a>
);
}

export function Journal({
venue,
volume,
issue,
className,
}: {
venue?: Required<PageFrontmatter>['venue'];
volume?: Required<PageFrontmatter>['volume'];
issue?: Required<PageFrontmatter>['issue'];
className?: string;
}) {
if (!venue) return null;
const { title, url } = typeof venue === 'string' ? { title: venue, url: null } : venue;
if (!title) return null;
return (
<div className={classNames('flex-none mr-2', className)}>
{url ? (
<ExternalOrInternalLink
className="font-semibold no-underline smallcaps"
to={url}
title={title}
>
{title}
</ExternalOrInternalLink>
) : (
<span className="font-semibold smallcaps">{title}</span>
)}
{volume != null && (
<span className="pl-2 ml-2 border-l">
Volume {volume.title}
{issue != null && <>, Issue {issue.title}</>}
</span>
)}
</div>
);
}

export function FrontmatterBlock({
frontmatter,
kind = SourceFileKind.Article,
authorStyle = 'block',
hideBadges,
hideExports,
className,
thebe,
location,
}: {
frontmatter: Omit<PageFrontmatter, 'parts'>;
kind?: SourceFileKind;
authorStyle?: 'block' | 'list';
hideBadges?: boolean;
hideExports?: boolean;
className?: string;
thebe?: ExpandedThebeFrontmatter;
location?: string;
}) {
if (!frontmatter) return null;
const {
title,
subtitle,
subject,
doi,
open_access,
license,
github,
venue,
volume,
issue,
exports,
downloads,
date,
authors,
enumerator,
} = frontmatter;
const isJupyter = kind === SourceFileKind.Notebook;
const hasExports = downloads ? downloads.length > 0 : exports && exports.length > 0;
const hasAuthors = authors && authors.length > 0;
const hasBadges = !!open_access || !!license || !!hasExports || !!isJupyter || !!github;
const hasHeaders = !!subject || !!venue || !!volume || !!issue;
const hasDateOrDoi = !!doi || !!date;
const showHeaderBlock = hasHeaders || (hasBadges && !hideBadges) || (hasExports && !hideExports);
const hideLaunch: boolean = false;

if (!title && !subtitle && !showHeaderBlock && !hasAuthors && !hasDateOrDoi) {
// Nothing to show!
return null;
}
return (
<div
id="skip-to-frontmatter"
aria-label="article frontmatter"
className={classNames(className)}
>
{showHeaderBlock && (
<div className="flex items-center mb-5 h-6 text-sm font-light">
{subject && (
<div
className={classNames('flex-none pr-2 smallcaps', {
'mr-2 border-r': venue,
})}
>
{subject}
</div>
)}
<Journal venue={venue} volume={volume} issue={issue} />
<div className="flex-grow"></div>
{!hideBadges && (
<>
{/* <LicenseBadges license={license} /> */}
<OpenAccessBadge open_access={open_access} />
<GitHubLink github={github} />
{isJupyter && (
<div className="inline-block mr-1">
<JupyterIcon
width="1.25rem"
height="1.25rem"
className="inline-block"
title="Jupyter Notebook"
/>
</div>
)}
</>
)}
</div>
)}
{title && (
<h1 className="mb-0">
{enumerator && <span className="mr-3 select-none">{enumerator}</span>}
{title}
</h1>
)}
{subtitle && <p className="mt-2 mb-0 lead text-zinc-600 dark:text-zinc-400">{subtitle}</p>}
{/* {hasAuthors && authorStyle === 'list' && (
<AuthorsList authors={frontmatter.authors} affiliations={frontmatter.affiliations} />
)} */}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Authors List Component Missing

The AuthorsList component, intended for rendering authors when authorStyle is set to 'list', is currently commented out in FrontmatterBlock.tsx. This prevents authors from being displayed in components, such as ArticlePage, that pass authorStyle="list".

Fix in Cursor Fix in Web

{hasAuthors && authorStyle === 'block' && (
<AuthorAndAffiliations
authors={frontmatter.authors}
affiliations={frontmatter.affiliations}
/>
)}
{hasDateOrDoi && (
<div className="flex mt-2 text-sm font-light">
<DateString date={date} spacer={!!doi} />
<DoiBadge doi={doi} />
</div>
)}
</div>
);
}
45 changes: 45 additions & 0 deletions theme/app/routes/$id[.png].ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { type LoaderFunction } from '@remix-run/node';
import { getConfig, getPage } from '~/utils/loaders.server';
import { NotFoundError } from '../utils/errors';

const OG_API = 'https://og.curvenote.com/';

export const loader: LoaderFunction = async ({ request, params }) => {
const id = params.id?.toUpperCase();
if (!id || !id.match(/^PMC[0-9]*$/)) throw NotFoundError();
const [config, article] = await Promise.all([getConfig(id), getPage(request, id, {})]);
if (!config || !article) throw NotFoundError();
const title = `${config?.projects?.[0].title}`;
const authors = config?.projects?.[0].authors;
const date = config?.projects?.[0].date;
const thumbnail = article?.frontmatter.thumbnail ?? undefined;
console.log(thumbnail);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Debugging Code Left in Production

A console.log statement logging the thumbnail value is present in the production code.

Fix in Cursor Fix in Web

const url = new URL(`${OG_API}api/journal`);
const pmcLogo = 'https://upload.wikimedia.org/wikipedia/commons/f/fb/US-NLM-PubMed-Logo.svg';
url.searchParams.set('logo', pmcLogo);
url.searchParams.set('title', title.replace(/<\/?[a-zA-Z0-9-]+>/g, ''));
if (authors && authors.length) {
url.searchParams.set('authors', authors.map((a) => a.name).join(', '));
}
url.searchParams.set('subject', id);
url.searchParams.set('theme', '#542D59');

if (thumbnail) url.searchParams.set('image', thumbnail);
const dateString = (date ? new Date(date) : new Date()).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
timeZone: 'UTC',
});
url.searchParams.set('date', dateString);
// 'https://og-pearl.vercel.app/api/physiome?title=Bond%20Graph%20Model%20of%20Cerebral%20Circulation%3A%20Toward%20Clinically%20Feasible%20Systemic%20Blood%20Flow%20Simulations&date=August%2021%2C%202020&image=https%3A%2F%2Fphysiome.curve.space%2Fstatic%2Fthumbnails%2FS000001.png&authors=Shan%20Su%2C%20Pablo%20J.%20Blanco%2C%20Lucas%20O.%20M%C3%BCller%2C%20Peter%20J.%20Hunter%2C%20Soroush%20Safaei&subject=Original%20Submission',
const ogUrl = url.toString();
const resp = await fetch(ogUrl);

return new Response(await resp.arrayBuffer(), {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'max-age=86400',
},
});
};
11 changes: 4 additions & 7 deletions theme/app/routes/($id).tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,7 @@ export const loader: LoaderFunction = async ({ params, request }) => {
const { id } = params;
if (!id) throw NotFoundError();
try {
const config = await getConfig(id);
const article = await getPage(request, id, {});
if (article && id.match(/^PMC[0-9]+$/)) {
article.frontmatter.identifiers ??= {};
article.frontmatter.identifiers.pmcid = id;
}
const [config, article] = await Promise.all([getConfig(id), getPage(request, id, {})]);
return { config, article, id };
} catch (error) {
const { status } = await getProcessingStatus(id);
Expand Down Expand Up @@ -118,6 +113,7 @@ export default function Page() {
...siteDesign,
...pageDesign,
};

return (
<ArticlePageAndNavigation hide_toc={hide_toc} hideSearch>
<SiteProvider config={config}>
Expand All @@ -126,13 +122,14 @@ export default function Page() {
<GitHubIssueButton id={id} />
{!hide_outline && (
<div
className="sticky z-10 hidden h-0 col-margin-right-inset lg:block"
className="hidden sticky z-10 h-0 col-margin-right-inset lg:block"
style={{ top: 0 }}
>
<DocumentOutline
className="relative pt-5 ml-6 max-w-[350px]"
outlineRef={outline}
isMargin={false}
maxdepth={1}
/>
</div>
)}
Expand Down
Loading
Loading