-
-
Notifications
You must be signed in to change notification settings - Fork 1
feat: git history in lecture headers (PLAN Phase 1) #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mmcky
wants to merge
8
commits into
main
Choose a base branch
from
feat/git-history-headers
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
0dfc2c4
feat: git history in lecture headers (PLAN Phase 1)
mmcky e007bf7
test: refresh CI visual snapshots (linux baselines)
github-actions[bot] d68317a
fix(header): guard full-history link shape; plain text when changelog…
mmcky 6311f6c
Merge main into feat/git-history-headers (post #84/#85); combine [Unr…
mmcky 29fbc27
Merge main into feat/git-history-headers (post #86); README keeps Col…
mmcky 2b5b3fe
feat(header): centred changelog modal + last-changed inline with authors
mmcky 8c0229f
test: refresh CI visual snapshots (linux baselines)
github-actions[bot] eae42d2
ci: nudge run on refreshed linux baselines (snapshot-bot push doesn't…
mmcky File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| import * as Popover from '@radix-ui/react-popover'; | ||
| import { ChevronDown } from 'lucide-react'; | ||
| import React from 'react'; | ||
| import { usePage } from './PageProvider'; | ||
|
|
||
| export interface GitChangelogEntry { | ||
| hash: string; | ||
| short_hash: string; | ||
| author: string; | ||
| date: string; | ||
| message: string; | ||
| } | ||
|
|
||
| export interface GitMetadata { | ||
| last_modified?: string; | ||
| changelog?: GitChangelogEntry[]; | ||
| } | ||
|
|
||
| const DATE_FORMAT = new Intl.DateTimeFormat('en-US', { | ||
| month: 'short', | ||
| day: 'numeric', | ||
| year: 'numeric', | ||
| // Pin the timezone so the server render, client hydration, and visual | ||
| // snapshots agree on the date regardless of host timezone. | ||
| timeZone: 'UTC', | ||
| }); | ||
|
|
||
| function formatDate(iso: string) { | ||
| const date = new Date(iso); | ||
| return Number.isNaN(date.getTime()) ? iso : DATE_FORMAT.format(date); | ||
| } | ||
|
|
||
| function relativeTime(iso: string, now: number) { | ||
| const seconds = (now - new Date(iso).getTime()) / 1000; | ||
| if (Number.isNaN(seconds) || seconds < 60) return 'just now'; | ||
| const units: [number, string][] = [ | ||
| [3600, 'minute'], | ||
| [86400, 'hour'], | ||
| [604800, 'day'], | ||
| [2592000, 'week'], | ||
| [31536000, 'month'], | ||
| [Infinity, 'year'], | ||
| ]; | ||
| let size = 60; | ||
| for (const [limit, label] of units) { | ||
| if (seconds < limit) { | ||
| const count = Math.floor(seconds / size); | ||
| return `${count} ${label}${count !== 1 ? 's' : ''} ago`; | ||
| } | ||
| size = limit; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * "Last changed" page-header control with a changelog dropdown, mirroring the | ||
| * quantecon-book-theme header. | ||
| * | ||
| * Data sources, in order of precedence: | ||
| * 1. `site.git_metadata` in the page frontmatter (manual override, and how | ||
| * the visual fixture pins deterministic data), then | ||
| * 2. `mdast.data.git_metadata` injected at build time by | ||
| * plugins/git-metadata.mjs. | ||
| * | ||
| * Renders nothing when neither is present. | ||
| */ | ||
| export function PageHeaderHistory() { | ||
| const page = usePage(); | ||
| // Relative times depend on the clock; render absolute dates on the server | ||
| // and only switch after mount so statically exported HTML hydrates cleanly. | ||
| const [now, setNow] = React.useState<number | null>(null); | ||
| React.useEffect(() => setNow(Date.now()), []); | ||
|
|
||
| const frontmatter = page?.frontmatter as any; | ||
| const meta: GitMetadata | undefined = | ||
| frontmatter?.site?.git_metadata ?? (page?.mdast as any)?.data?.git_metadata; | ||
| const changelog = meta?.changelog ?? []; | ||
| const lastModified = meta?.last_modified ?? changelog[0]?.date; | ||
| if (!lastModified) return null; | ||
|
|
||
| // Commit links target the source repository itself, so unlike | ||
| // LaunchButton's notebook URLs the `.myst` suffix must be kept. | ||
| const github: string | undefined = frontmatter?.github; | ||
| const repoUrl = github?.startsWith('https://github.com/') ? github.replace(/\/$/, '') : undefined; | ||
| // mystmd already computes `source_url` as {repo}/blob/{branch}/{path}. | ||
| const historyUrl = (frontmatter?.source_url as string | undefined)?.replace( | ||
| '/blob/', | ||
| '/commits/' | ||
| ); | ||
|
|
||
| return ( | ||
| <Popover.Root> | ||
| <Popover.Trigger | ||
| className="group flex items-center gap-1 text-sm cursor-pointer | ||
| text-qetext-light/70 dark:text-qetext-dark-muted | ||
| hover:text-qeborder-blue dark:hover:text-qeborder-blue" | ||
| > | ||
|
mmcky marked this conversation as resolved.
Outdated
|
||
| Last changed: {formatDate(lastModified)} | ||
| {changelog.length > 0 && ( | ||
| <ChevronDown | ||
| size={14} | ||
| aria-hidden | ||
| className="transition-transform group-data-[state=open]:rotate-180" | ||
| /> | ||
| )} | ||
| </Popover.Trigger> | ||
| {changelog.length > 0 && ( | ||
| <Popover.Portal> | ||
| <Popover.Content | ||
| align="start" | ||
| sideOffset={3} | ||
| className={` | ||
| z-10 w-[420px] max-w-[90vw] rounded bg-white dark:bg-qepage-dark p-4 | ||
| text-qetext-light dark:text-qetext-dark | ||
| will-change-[transform,opacity] | ||
| shadow-md | ||
| dark:shadow-sm | ||
| dark:shadow-white/20 | ||
| `} | ||
| > | ||
| <Popover.Arrow className="shadow-md stroke-2 fill-white dark:fill-qepage-dark" /> | ||
| <div className="flex items-baseline justify-between pb-2 border-b border-qetoolbar-border"> | ||
| <span className="text-base font-medium">Changelog</span> | ||
| {historyUrl && ( | ||
| <a | ||
| href={historyUrl} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="text-xs text-qeborder-blue hover:underline" | ||
| > | ||
| full history | ||
| </a> | ||
| )} | ||
| </div> | ||
| <ol className="m-0 p-0 list-none max-h-80 overflow-y-auto" aria-label="Recent changes"> | ||
| {changelog.map((entry) => ( | ||
| <li | ||
| key={entry.hash} | ||
| className="py-1.5 border-b border-qetoolbar-border/50 last:border-b-0" | ||
| > | ||
| <div className="flex items-baseline gap-2"> | ||
| {repoUrl ? ( | ||
| <a | ||
| href={`${repoUrl}/commit/${entry.hash}`} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="font-mono text-xs text-qeborder-blue hover:underline" | ||
| > | ||
| {entry.short_hash} | ||
| </a> | ||
| ) : ( | ||
| <span className="font-mono text-xs opacity-70">{entry.short_hash}</span> | ||
| )} | ||
| <span className="flex-1 text-sm truncate" title={entry.message}> | ||
| {entry.message} | ||
| </span> | ||
| </div> | ||
| <div className="flex gap-1 text-xs opacity-70"> | ||
| <span>{entry.author}</span> | ||
| <span aria-hidden>·</span> | ||
| <span>{now ? relativeTime(entry.date, now) : formatDate(entry.date)}</span> | ||
| </div> | ||
| </li> | ||
| ))} | ||
| </ol> | ||
| </Popover.Content> | ||
| </Popover.Portal> | ||
| )} | ||
| </Popover.Root> | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,16 @@ | ||
| import type { GitMetadata } from './components/PageHeaderHistory'; | ||
|
|
||
| export interface TemplateOptions { | ||
| hide_toc?: boolean; | ||
| hide_outline?: boolean; | ||
| hide_search?: boolean; | ||
| hide_footer_links?: boolean; | ||
| outline_maxdepth?: number; | ||
| hide_title_block?: boolean; | ||
| /** | ||
| * Page-level override for the "Last changed" header control, normally | ||
| * injected at build time by plugins/git-metadata.mjs (set under `site:` in | ||
| * page frontmatter). | ||
| */ | ||
| git_metadata?: GitMetadata; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.