This commit is contained in:
@@ -13,15 +13,70 @@ const props = defineProps<{
|
||||
* keep two articles' "Proof" headings from sharing one id.
|
||||
*/
|
||||
namespace?: string
|
||||
/**
|
||||
* The article's own language: callout boxes (```theorem, …) label
|
||||
* themselves in it, whatever language the interface happens to speak.
|
||||
*/
|
||||
articleLang?: string
|
||||
}>()
|
||||
|
||||
const rendered = computed(() => renderMarkdown(props.content, props.namespace))
|
||||
const rendered = computed(() => renderMarkdown(props.content, props.namespace, props.articleLang))
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
/* Callout boxes fold away under their label. The browser's <details> carries
|
||||
the state, but a state flip alone has no motion: the toggle click is caught
|
||||
here and the fold — height and opacity of the box's body — runs through the
|
||||
Web Animations API. Opening sets `open` right away (the body needs to be
|
||||
laid out before it can be measured); closing flips it only once the body
|
||||
has folded away. The box's intended state rides along as data-open, so
|
||||
re-clicking mid-fold picks the animation up where it stands instead of
|
||||
fighting it. */
|
||||
const folds = new WeakMap<HTMLDetailsElement, Animation>()
|
||||
|
||||
function foldBox(details: HTMLDetailsElement, event: MouseEvent) {
|
||||
const body = details.querySelector<HTMLElement>('.md-box-body')
|
||||
const isOpen = details.dataset.open ?? String(details.open)
|
||||
const wantOpen = isOpen !== 'true'
|
||||
details.dataset.open = String(wantOpen)
|
||||
|
||||
// nothing to shrink, or the reader asks for no motion: let <details> answer
|
||||
if (!body || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
|
||||
|
||||
event.preventDefault()
|
||||
const running = folds.get(details)
|
||||
// a fold in flight shows its height — the new animation starts there
|
||||
const from = running ? getComputedStyle(body).height : wantOpen ? '0px' : `${body.scrollHeight}px`
|
||||
running?.cancel()
|
||||
if (wantOpen) details.open = true
|
||||
|
||||
body.style.overflow = 'hidden'
|
||||
const anim = body.animate(
|
||||
[
|
||||
{ height: from, opacity: wantOpen ? 0 : 1 },
|
||||
{ height: wantOpen ? `${body.scrollHeight}px` : '0px', opacity: wantOpen ? 1 : 0 },
|
||||
],
|
||||
{ duration: 260, easing: 'cubic-bezier(0.33, 1, 0.68, 1)' },
|
||||
)
|
||||
folds.set(details, anim)
|
||||
anim.onfinish = () => {
|
||||
details.open = wantOpen
|
||||
body.style.overflow = ''
|
||||
folds.delete(details)
|
||||
}
|
||||
}
|
||||
|
||||
function onClick(e: MouseEvent) {
|
||||
if (!props.interceptLinks) return
|
||||
const target = e.target as HTMLElement
|
||||
const summary = target.closest?.('summary')
|
||||
if (summary) {
|
||||
const details = summary.closest('details')
|
||||
if (details) {
|
||||
foldBox(details, e)
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!props.interceptLinks) return
|
||||
const anchor = target.closest?.('a')
|
||||
if (!anchor) return
|
||||
const href = anchor.getAttribute('href') || ''
|
||||
|
||||
Reference in New Issue
Block a user