diff --git a/.changeset/curvy-geese-retire.md b/.changeset/curvy-geese-retire.md new file mode 100644 index 000000000..b4e33c6fc --- /dev/null +++ b/.changeset/curvy-geese-retire.md @@ -0,0 +1,5 @@ +--- +"myst-to-typst": patch +--- + +Add exercise and solution handlers diff --git a/packages/myst-to-typst/src/index.ts b/packages/myst-to-typst/src/index.ts index 391c45a43..7052360a6 100644 --- a/packages/myst-to-typst/src/index.ts +++ b/packages/myst-to-typst/src/index.ts @@ -23,6 +23,7 @@ import MATH_HANDLERS, { resolveRecursiveCommands } from './math.js'; import { select, selectAll } from 'unist-util-select'; import type { Admonition, Code, CrossReference, FootnoteDefinition, TabItem } from 'myst-spec-ext'; import { tableCellHandler, tableHandler, tableRowHandler } from './table.js'; +import { proofHandlers } from './proofs.js'; export type { TypstResult } from './types.js'; @@ -73,26 +74,6 @@ const tabItem = ` ]) }`; -const proof = ` -#let proof(body, heading: [], kind: "proof", supplement: "Proof", labelName: none, color: blue, float: true) = { - let stroke = 1pt + color.lighten(90%) - let fill = color.lighten(90%) - let title - set figure.caption(position: top) - set figure(placement: none) - show figure.caption.where(body: heading): (it) => { - block(width: 100%, stroke: stroke, fill: fill, inset: 8pt, it) - } - place(auto, float: float, block(width: 100%, [ - #figure(kind: kind, supplement: supplement, gap: 0pt, [ - #set align(left); - #set figure.caption(position: bottom) - #block(width: 100%, fill: luma(253), stroke: stroke, inset: 8pt)[#body] - ], caption: heading) - #if(labelName != none){label(labelName)} - ])) -}`; - const INDENT = ' '; const linkHandler = (node: any, state: ITypstSerializer) => { @@ -456,25 +437,6 @@ const handlers: Record = { state.renderChildren(node); state.write('\n]\n\n'); }, - proof(node: GenericNode, state) { - state.useMacro(proof); - const title = select('admonitionTitle', node); - const kind = node.kind || 'proof'; - const supplement = getDefaultCaptionSupplement(kind); - state.write( - `#proof(kind: "${kind}", supplement: "${supplement}", labelName: ${node.identifier ? `"${node.identifier}"` : 'none'}`, - ); - if (title) { - state.write(', heading: ['); - state.renderChildren(title); - state.write('])['); - } else { - state.write(')['); - } - state.renderChildren(node); - state.write(']'); - state.ensureNewLine(); - }, card(node, state) { if (node.url) { node.children?.push({ type: 'paragraph', children: [{ type: 'text', value: node.url }] }); @@ -496,6 +458,7 @@ const handlers: Record = { footer() { return; }, + ...proofHandlers, }; class TypstSerializer implements ITypstSerializer { diff --git a/packages/myst-to-typst/src/proofs.ts b/packages/myst-to-typst/src/proofs.ts new file mode 100644 index 000000000..1bc2bd842 --- /dev/null +++ b/packages/myst-to-typst/src/proofs.ts @@ -0,0 +1,55 @@ +import { type GenericNode } from 'myst-common'; +import type { Handler, ITypstSerializer } from './types.js'; +import { getDefaultCaptionSupplement } from './container.js'; +import { select } from 'unist-util-select'; + +const proof = ` +#let proof(body, heading: [], kind: "proof", supplement: "Proof", labelName: none, color: blue, float: true) = { + let stroke = 1pt + color.lighten(90%) + let fill = color.lighten(90%) + let title + set figure.caption(position: top) + set figure(placement: none) + show figure.caption.where(body: heading): (it) => { + block(width: 100%, stroke: stroke, fill: fill, inset: 8pt, it) + } + place(auto, float: float, block(width: 100%, [ + #figure(kind: kind, supplement: supplement, gap: 0pt, [ + #set align(left); + #set figure.caption(position: bottom) + #block(width: 100%, fill: luma(253), stroke: stroke, inset: 8pt)[#body] + ], caption: heading) + #if(labelName != none){label(labelName)} + ])) +}`; + +function writeProof(node: GenericNode, state: ITypstSerializer, kind: string) { + state.useMacro(proof); + const title = select('admonitionTitle', node); + const supplement = getDefaultCaptionSupplement(kind); + state.write( + `#proof(kind: "${kind}", supplement: "${supplement}", labelName: ${node.identifier ? `"${node.identifier}"` : 'none'}`, + ); + if (title) { + state.write(', heading: ['); + state.renderChildren(title); + state.write('])[\n'); + } else { + state.write(')[\n'); + } + state.renderChildren(node); + state.write(']'); + state.ensureNewLine(); +} + +export const proofHandlers: Record = { + proof(node: GenericNode, state) { + writeProof(node, state, node.kind || 'proof'); + }, + exercise(node: GenericNode, state) { + writeProof(node, state, 'exercise'); + }, + solution(node: GenericNode, state) { + writeProof(node, state, 'solution'); + }, +}; diff --git a/packages/myst-to-typst/tests/proofs.yml b/packages/myst-to-typst/tests/proofs.yml new file mode 100644 index 000000000..6c4b1a681 --- /dev/null +++ b/packages/myst-to-typst/tests/proofs.yml @@ -0,0 +1,16 @@ +title: myst-to-typst code +cases: + - title: proof + mdast: + type: root + children: + - type: exercise + identifier: my-exercise + children: + - type: paragraph + children: + - type: text + value: Recall... + typst: |- + #proof(kind: "exercise", supplement: "Exercise", labelName: "my-exercise")[ + Recall...]