@airalogy/aimd-renderer 2.7.0 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,265 @@
1
+ import type { Element, Root as HastRoot, RootContent, Text as HastText } from "hast"
2
+ import type { AimdNode, AimdReferenceEntry, ExtractedAimdFields } from "@airalogy/aimd-core/types"
3
+
4
+ export interface AimdCitationReferenceDisplay {
5
+ id: string
6
+ label: string
7
+ summary: string
8
+ }
9
+
10
+ type AimdCitationNodeWithDisplay = AimdNode & {
11
+ refs?: string[]
12
+ citationRefs?: AimdCitationReferenceDisplay[]
13
+ }
14
+
15
+ function isElementNode(node: RootContent | HastRoot): node is Element {
16
+ return node.type === "element"
17
+ }
18
+
19
+ function createTextNode(value: string): HastText {
20
+ return { type: "text", value }
21
+ }
22
+
23
+ function readStringProperty(properties: Record<string, unknown>, keys: string[]): string | undefined {
24
+ for (const key of keys) {
25
+ const value = properties[key]
26
+ if (typeof value === "string") {
27
+ return value
28
+ }
29
+ }
30
+ return undefined
31
+ }
32
+
33
+ function splitCsv(value: string | undefined): string[] {
34
+ return (value || "")
35
+ .split(",")
36
+ .map(part => part.trim())
37
+ .filter(Boolean)
38
+ }
39
+
40
+ function hasClass(element: Element, className: string): boolean {
41
+ const value = element.properties?.className ?? element.properties?.class
42
+ if (Array.isArray(value)) {
43
+ return value.includes(className)
44
+ }
45
+ if (typeof value === "string") {
46
+ return value.split(/\s+/).includes(className)
47
+ }
48
+ return false
49
+ }
50
+
51
+ function isReferenceSectionElement(element: Element): boolean {
52
+ const properties = element.properties || {}
53
+ const aimdType = properties["data-aimd-type"] || properties.dataAimdType
54
+ const aimdData = (element.data as { aimd?: AimdNode } | undefined)?.aimd
55
+ return aimdType === "refs" || aimdData?.fieldType === "refs" || hasClass(element, "aimd-refs")
56
+ }
57
+
58
+ function isCitationElement(element: Element): boolean {
59
+ const properties = element.properties || {}
60
+ const aimdType = properties["data-aimd-type"] || properties.dataAimdType
61
+ const aimdData = (element.data as { aimd?: AimdNode } | undefined)?.aimd
62
+ return aimdType === "cite" || aimdData?.fieldType === "cite" || hasClass(element, "aimd-cite")
63
+ }
64
+
65
+ function getReferenceContainer(entry: AimdReferenceEntry): string | undefined {
66
+ return entry.journal || entry.booktitle || entry.publisher
67
+ }
68
+
69
+ export function formatCitationReferenceSummary(entry: AimdReferenceEntry | undefined, fallbackId: string): string {
70
+ if (!entry) {
71
+ return fallbackId
72
+ }
73
+
74
+ const parts = [
75
+ entry.author,
76
+ entry.year ? `(${entry.year})` : undefined,
77
+ entry.title,
78
+ getReferenceContainer(entry),
79
+ entry.doi ? `doi:${entry.doi}` : undefined,
80
+ entry.url,
81
+ ]
82
+ .map(part => part?.trim())
83
+ .filter((part): part is string => Boolean(part))
84
+
85
+ return parts.length > 0 ? parts.join(" ") : fallbackId
86
+ }
87
+
88
+ function getCitationRefIds(element: Element): string[] {
89
+ const aimdData = (element.data as { aimd?: AimdCitationNodeWithDisplay } | undefined)?.aimd
90
+ if (aimdData?.fieldType === "cite" && Array.isArray(aimdData.refs)) {
91
+ return aimdData.refs.map(ref => String(ref).trim()).filter(Boolean)
92
+ }
93
+
94
+ const properties = element.properties || {}
95
+ const refs = readStringProperty(properties as Record<string, unknown>, ["data-aimd-refs", "dataAimdRefs"])
96
+ const parsedRefs = splitCsv(refs)
97
+ if (parsedRefs.length > 0) {
98
+ return parsedRefs
99
+ }
100
+
101
+ const id = readStringProperty(properties as Record<string, unknown>, ["data-aimd-id", "dataAimdId"])
102
+ return id ? [id] : []
103
+ }
104
+
105
+ function buildCitationLabelMap(fields: ExtractedAimdFields): Map<string, string> {
106
+ const labels = new Map<string, string>()
107
+ for (const [index, entry] of (fields.refs || []).entries()) {
108
+ const id = entry.id?.trim()
109
+ if (id && !labels.has(id)) {
110
+ labels.set(id, String(index + 1))
111
+ }
112
+ }
113
+ return labels
114
+ }
115
+
116
+ function buildCitationSummaryMap(fields: ExtractedAimdFields): Map<string, string> {
117
+ const summaries = new Map<string, string>()
118
+ for (const entry of fields.refs || []) {
119
+ const id = entry.id?.trim()
120
+ if (id && !summaries.has(id)) {
121
+ summaries.set(id, formatCitationReferenceSummary(entry, id))
122
+ }
123
+ }
124
+ return summaries
125
+ }
126
+
127
+ function buildCitationRefs(
128
+ refIds: string[],
129
+ labelByRefId: Map<string, string>,
130
+ summaryByRefId: Map<string, string>,
131
+ ): AimdCitationReferenceDisplay[] {
132
+ return refIds.map(id => ({
133
+ id,
134
+ label: labelByRefId.get(id) || id,
135
+ summary: summaryByRefId.get(id) || id,
136
+ }))
137
+ }
138
+
139
+ function createCitationReferenceElement(citationRef: AimdCitationReferenceDisplay): Element {
140
+ return {
141
+ type: "element",
142
+ tagName: "span",
143
+ properties: {
144
+ className: ["aimd-cite__ref"],
145
+ role: "doc-noteref",
146
+ tabIndex: 0,
147
+ title: citationRef.summary,
148
+ "data-aimd-ref-id": citationRef.id,
149
+ "data-aimd-ref-summary": citationRef.summary,
150
+ "aria-label": citationRef.label === citationRef.id
151
+ ? `Reference ${citationRef.summary}`
152
+ : `Reference ${citationRef.label}: ${citationRef.summary}`,
153
+ },
154
+ children: [
155
+ {
156
+ type: "element",
157
+ tagName: "span",
158
+ properties: { className: ["aimd-cite__label"] },
159
+ children: [createTextNode(citationRef.label)],
160
+ } as Element,
161
+ {
162
+ type: "element",
163
+ tagName: "span",
164
+ properties: {
165
+ className: ["aimd-cite__popover"],
166
+ role: "tooltip",
167
+ },
168
+ children: [createTextNode(citationRef.summary)],
169
+ } as Element,
170
+ ],
171
+ } as Element
172
+ }
173
+
174
+ function createCitationChildren(citationRefs: AimdCitationReferenceDisplay[]): Array<Element | HastText> {
175
+ const children: Array<Element | HastText> = [createTextNode("[")]
176
+
177
+ citationRefs.forEach((citationRef, index) => {
178
+ if (index > 0) {
179
+ children.push(createTextNode(", "))
180
+ }
181
+
182
+ children.push(createCitationReferenceElement(citationRef))
183
+ })
184
+
185
+ children.push(createTextNode("]"))
186
+ return children
187
+ }
188
+
189
+ function annotateCitationElement(element: Element, citationRefs: AimdCitationReferenceDisplay[]): void {
190
+ const properties = element.properties || {}
191
+ element.properties = {
192
+ ...properties,
193
+ "data-aimd-citation-labels": citationRefs.map(ref => ref.label).join(","),
194
+ "data-aimd-citation-summaries": JSON.stringify(citationRefs.map(ref => ref.summary)),
195
+ }
196
+
197
+ const data = ((element as any).data || {}) as { aimd?: AimdCitationNodeWithDisplay }
198
+ if (data.aimd?.fieldType === "cite") {
199
+ data.aimd.citationRefs = citationRefs
200
+ data.aimd.refs = citationRefs.map(ref => ref.id)
201
+ ;(element as any).data = data
202
+ }
203
+
204
+ element.children = createCitationChildren(citationRefs)
205
+ }
206
+
207
+ function walk(node: HastRoot | RootContent, visitor: (element: Element) => void): void {
208
+ if (!isElementNode(node) && node.type !== "root") {
209
+ return
210
+ }
211
+
212
+ if (isElementNode(node)) {
213
+ visitor(node)
214
+ }
215
+
216
+ const children = node.children || []
217
+ for (const child of children) {
218
+ walk(child, visitor)
219
+ }
220
+ }
221
+
222
+ function extractReferenceSections(children: RootContent[], sections: Element[]): RootContent[] {
223
+ const nextChildren: RootContent[] = []
224
+
225
+ for (const child of children) {
226
+ if (isElementNode(child)) {
227
+ if (isReferenceSectionElement(child)) {
228
+ sections.push(child)
229
+ continue
230
+ }
231
+
232
+ child.children = extractReferenceSections(child.children || [], sections) as Element["children"]
233
+ }
234
+
235
+ nextChildren.push(child)
236
+ }
237
+
238
+ return nextChildren
239
+ }
240
+
241
+ export function moveReferenceSectionsToEnd(tree: HastRoot): void {
242
+ const sections: Element[] = []
243
+ tree.children = extractReferenceSections(tree.children, sections)
244
+ if (sections.length > 0) {
245
+ tree.children.push(...sections)
246
+ }
247
+ }
248
+
249
+ export function annotateCitationReferenceLabels(tree: HastRoot, fields: ExtractedAimdFields): void {
250
+ const labelByRefId = buildCitationLabelMap(fields)
251
+ const summaryByRefId = buildCitationSummaryMap(fields)
252
+
253
+ walk(tree, (element) => {
254
+ if (!isCitationElement(element)) {
255
+ return
256
+ }
257
+
258
+ const refIds = getCitationRefIds(element)
259
+ if (refIds.length === 0) {
260
+ return
261
+ }
262
+
263
+ annotateCitationElement(element, buildCitationRefs(refIds, labelByRefId, summaryByRefId))
264
+ })
265
+ }
@@ -1,10 +1,12 @@
1
1
  import type { Element, Properties, Root as HastRoot, Text as HastText } from "hast"
2
2
  import type { VFile } from "vfile"
3
3
  import type { VNode } from "vue"
4
+ import type { AimdAssetUrlResolver } from "./assetUrls"
4
5
  import type {
5
6
  AimdFieldType,
6
7
  AimdNode,
7
8
  AimdQuizNode,
9
+ AimdReferenceEntry,
8
10
  AimdStepNode,
9
11
  ProcessorOptions,
10
12
  RenderContext,
@@ -13,6 +15,7 @@ import type { ExtractedAimdFields } from "@airalogy/aimd-core/types"
13
15
  import type { AimdRendererI18nOptions } from "../locales"
14
16
  import type { VueRendererOptions } from "../vue/vue-renderer"
15
17
  import { resolveQuizPreviewOptions } from "./quiz-preview"
18
+ import { resolveAimdAssetUrl } from "./assetUrls"
16
19
 
17
20
  // ---------------------------------------------------------------------------
18
21
  // Sub-module imports
@@ -21,6 +24,7 @@ import { resolveQuizPreviewOptions } from "./quiz-preview"
21
24
  import { remarkInsertVisibleAssigners, remarkStripAssignerCodeBlocks } from "./assignerVisibility"
22
25
  import { highlightVisibleAssigners } from "./assignerHighlighting"
23
26
  import { annotateStepReferenceSequence } from "./annotateStepReferences"
27
+ import { annotateCitationReferenceLabels, moveReferenceSectionsToEnd } from "./citationNumbering"
24
28
  import { criticMarkupHandlers } from "./criticMarkup"
25
29
  import { buildFigureChildren, assignFigureSequenceNumbers } from "./figureNumbering"
26
30
 
@@ -52,6 +56,7 @@ export type AimdHtmlNodeRenderer = (
52
56
  export interface AimdRendererOptions extends ProcessorOptions, AimdRendererI18nOptions {
53
57
  assignerVisibility?: AimdAssignerVisibility
54
58
  aimdElementRenderers?: Partial<Record<AimdFieldType, AimdHtmlNodeRenderer>>
59
+ resolveAssetUrl?: AimdAssetUrlResolver
55
60
  groupStepBodies?: boolean
56
61
  groupCheckBodies?: boolean
57
62
  blockVarTypes?: string[]
@@ -557,6 +562,7 @@ const EMPTY_EXTRACTED_FIELDS: ExtractedAimdFields = {
557
562
  ref_fig: [],
558
563
  cite: [],
559
564
  fig: [],
565
+ refs: [],
560
566
  }
561
567
 
562
568
  async function ensureMathStylesLoaded(mathEnabled: boolean | undefined): Promise<void> {
@@ -602,6 +608,7 @@ function createEmptyExtractedFields(): ExtractedAimdFields {
602
608
  ref_fig: [...(EMPTY_EXTRACTED_FIELDS.ref_fig || [])],
603
609
  cite: [...(EMPTY_EXTRACTED_FIELDS.cite || [])],
604
610
  fig: [...(EMPTY_EXTRACTED_FIELDS.fig || [])],
611
+ refs: [...(EMPTY_EXTRACTED_FIELDS.refs || [])],
605
612
  }
606
613
  }
607
614
 
@@ -807,6 +814,94 @@ function buildScaleBandChildren(quizNode: AimdQuizNode): Array<Element | HastTex
807
814
  } as Element]
808
815
  }
809
816
 
817
+ function getReferenceContainer(entry: AimdReferenceEntry): string | undefined {
818
+ return entry.journal || entry.booktitle || entry.publisher
819
+ }
820
+
821
+ function createReferenceTextChildren(entry: AimdReferenceEntry): Array<Element | HastText> {
822
+ const children: Array<Element | HastText> = []
823
+ const pushText = (value: string | undefined, suffix = "") => {
824
+ const normalized = value?.trim()
825
+ if (!normalized) {
826
+ return
827
+ }
828
+ if (children.length > 0) {
829
+ children.push(createTextNode(" "))
830
+ }
831
+ children.push(createTextNode(`${normalized}${suffix}`))
832
+ }
833
+
834
+ pushText(entry.author)
835
+ pushText(entry.year ? `(${entry.year})` : undefined)
836
+ pushText(entry.title, entry.title && !/[.!?]$/.test(entry.title) ? "." : "")
837
+ pushText(getReferenceContainer(entry), ".")
838
+
839
+ if (entry.doi) {
840
+ if (children.length > 0) {
841
+ children.push(createTextNode(" "))
842
+ }
843
+ const doiHref = entry.doi.startsWith("http")
844
+ ? entry.doi
845
+ : `https://doi.org/${entry.doi}`
846
+ children.push({
847
+ type: "element",
848
+ tagName: "a",
849
+ properties: {
850
+ className: ["aimd-refs__doi"],
851
+ href: doiHref,
852
+ },
853
+ children: [createTextNode(`doi:${entry.doi}`)],
854
+ } as Element)
855
+ }
856
+ else if (entry.url) {
857
+ if (children.length > 0) {
858
+ children.push(createTextNode(" "))
859
+ }
860
+ children.push({
861
+ type: "element",
862
+ tagName: "a",
863
+ properties: {
864
+ className: ["aimd-refs__url"],
865
+ href: entry.url,
866
+ },
867
+ children: [createTextNode(entry.url)],
868
+ } as Element)
869
+ }
870
+
871
+ if (children.length === 0) {
872
+ children.push(createTextNode(entry.id))
873
+ }
874
+
875
+ return children
876
+ }
877
+
878
+ function buildReferencesChildren(entries: AimdReferenceEntry[], title: string): Array<Element | HastText> {
879
+ return [
880
+ {
881
+ type: "element",
882
+ tagName: "div",
883
+ properties: { className: ["aimd-refs__title"] },
884
+ children: [createTextNode(title)],
885
+ } as Element,
886
+ {
887
+ type: "element",
888
+ tagName: "ol",
889
+ properties: { className: ["aimd-refs__list"] },
890
+ children: entries.map(entry => ({
891
+ type: "element",
892
+ tagName: "li",
893
+ properties: {
894
+ id: `ref-${entry.id}`,
895
+ className: ["aimd-refs__item"],
896
+ "data-aimd-ref-id": entry.id,
897
+ "data-aimd-ref-type": entry.entry_type,
898
+ },
899
+ children: createReferenceTextChildren(entry),
900
+ } as Element)),
901
+ } as Element,
902
+ ]
903
+ }
904
+
810
905
  function createTextNode(value: string): HastText {
811
906
  return { type: "text", value }
812
907
  }
@@ -994,6 +1089,16 @@ function createAimdHandler(options: AimdRendererOptions = {}) {
994
1089
  nodeData.sequence = figNode.sequence
995
1090
  }
996
1091
 
1092
+ // Add refs-specific fields
1093
+ if (node.fieldType === "refs") {
1094
+ nodeData.entries = (node as any).entries
1095
+ }
1096
+
1097
+ // Add cite-specific fields
1098
+ if (node.fieldType === "cite") {
1099
+ nodeData.refs = (node as any).refs
1100
+ }
1101
+
997
1102
  // Add step-specific fields
998
1103
  if (node.fieldType === "step") {
999
1104
  const stepNode = node as AimdStepNode
@@ -1026,12 +1131,13 @@ function createAimdHandler(options: AimdRendererOptions = {}) {
1026
1131
  const isCite = fieldType === "cite"
1027
1132
  const isQuiz = fieldType === "quiz"
1028
1133
  const isFig = fieldType === "fig"
1134
+ const isRefs = fieldType === "refs"
1029
1135
  const baseClass = isRef
1030
1136
  ? "aimd-ref"
1031
- : (isCite ? "aimd-cite" : (isFig ? "aimd-figure" : "aimd-field"))
1137
+ : (isCite ? "aimd-cite" : (isFig ? "aimd-figure" : (isRefs ? "aimd-refs" : "aimd-field")))
1032
1138
  const modifierClass = isRef
1033
1139
  ? `aimd-ref--${fieldType === "ref_step" ? "step" : (fieldType === "ref_var" ? "var" : "fig")}`
1034
- : (isCite ? "" : (isFig ? "" : `aimd-field--${typeClass}`))
1140
+ : (isCite ? "" : (isFig || isRefs ? "" : `aimd-field--${typeClass}`))
1035
1141
 
1036
1142
  // Generate children based on field type
1037
1143
  const children: (Element | HastText)[] = []
@@ -1091,27 +1197,66 @@ function createAimdHandler(options: AimdRendererOptions = {}) {
1091
1197
  }
1092
1198
  }
1093
1199
  else if (isCite) {
1094
- // Citation: [refs]
1200
+ // Citation: compact reference markers; details are attached during citation annotation.
1095
1201
  const refs = "refs" in node ? (node as any).refs : [id]
1202
+ children.push(createTextNode("["))
1203
+ refs.forEach((ref: string, index: number) => {
1204
+ if (index > 0) {
1205
+ children.push(createTextNode(", "))
1206
+ }
1207
+ children.push({
1208
+ type: "element",
1209
+ tagName: "span",
1210
+ properties: {
1211
+ className: ["aimd-cite__ref"],
1212
+ role: "doc-noteref",
1213
+ tabIndex: 0,
1214
+ title: ref,
1215
+ "data-aimd-ref-id": ref,
1216
+ "data-aimd-ref-summary": ref,
1217
+ },
1218
+ children: [
1219
+ {
1220
+ type: "element",
1221
+ tagName: "span",
1222
+ properties: { className: ["aimd-cite__label"] },
1223
+ children: [createTextNode(ref)],
1224
+ } as Element,
1225
+ {
1226
+ type: "element",
1227
+ tagName: "span",
1228
+ properties: {
1229
+ className: ["aimd-cite__popover"],
1230
+ role: "tooltip",
1231
+ },
1232
+ children: [createTextNode(ref)],
1233
+ } as Element,
1234
+ ],
1235
+ } as Element)
1236
+ })
1237
+ children.push(createTextNode("]"))
1238
+ }
1239
+ else if (isRefs) {
1240
+ const entries = Array.isArray((node as any).entries) ? (node as any).entries : []
1096
1241
  children.push({
1097
1242
  type: "element",
1098
- tagName: "span",
1099
- properties: { className: ["aimd-cite__refs"] },
1100
- children: [{ type: "text", value: `[${refs.join(", ")}]` }],
1243
+ tagName: "div",
1244
+ properties: { className: ["aimd-refs__content"] },
1245
+ children: buildReferencesChildren(entries, messages.references.title),
1101
1246
  } as Element)
1102
1247
  }
1103
- else if (fieldType === "var") {
1104
- // Variable: type label + id + optional type annotation
1105
- const definition = "definition" in node ? node.definition : undefined
1106
- children.push(
1248
+ else if (fieldType === "var") {
1249
+ // Variable: type label + id + optional type annotation
1250
+ const definition = "definition" in node ? node.definition : undefined
1251
+ children.push(
1107
1252
  {
1108
1253
  type: "element",
1109
1254
  tagName: "span",
1110
- properties: { className: ["aimd-field__scope"] },
1111
- children: [{ type: "text", value: getAimdRendererScopeLabel("var", messages) }],
1112
- } as Element,
1113
- createFieldNameElement(id, definition),
1114
- )
1255
+ properties: { className: ["aimd-field__scope"] },
1256
+ children: [{ type: "text", value: getAimdRendererScopeLabel("var", messages) }],
1257
+ } as Element,
1258
+ createFieldNameElement(id, definition),
1259
+ )
1115
1260
  if (definition?.type) {
1116
1261
  children.push({
1117
1262
  type: "element",
@@ -1358,9 +1503,15 @@ function createAimdHandler(options: AimdRendererOptions = {}) {
1358
1503
  else if (fieldType === "fig") {
1359
1504
  // Figure: delegate to figureNumbering module
1360
1505
  const figNode = node as any
1506
+ const figId = figNode.id || id
1507
+ const figSrc = resolveAimdAssetUrl(figNode.src, options.resolveAssetUrl, {
1508
+ kind: "fig",
1509
+ id: figId,
1510
+ title: figNode.title,
1511
+ })
1361
1512
  children.push(...buildFigureChildren({
1362
- id: figNode.id || id,
1363
- src: figNode.src,
1513
+ id: figId,
1514
+ src: figSrc,
1364
1515
  title: figNode.title,
1365
1516
  legend: figNode.legend,
1366
1517
  sequence: figNode.sequence,
@@ -1392,16 +1543,20 @@ function createAimdHandler(options: AimdRendererOptions = {}) {
1392
1543
  }
1393
1544
  }
1394
1545
 
1395
- // Add reference href
1546
+ // Add internal reference metadata for host-level scrolling.
1396
1547
  if (isRef) {
1548
+ properties.title = id
1549
+ properties.tabIndex = 0
1550
+ properties["data-aimd-ref-target"] = id
1551
+
1397
1552
  if (fieldType === "ref_step") {
1398
- properties.href = `#step-${id}`
1553
+ properties["data-aimd-ref-kind"] = "step"
1399
1554
  }
1400
1555
  else if (fieldType === "ref_var") {
1401
- properties.href = `#var-${id}`
1556
+ properties["data-aimd-ref-kind"] = "var"
1402
1557
  }
1403
1558
  else if (fieldType === "ref_fig") {
1404
- properties.href = `#fig-${id}`
1559
+ properties["data-aimd-ref-kind"] = "fig"
1405
1560
  }
1406
1561
  }
1407
1562
 
@@ -1463,6 +1618,15 @@ function createAimdHandler(options: AimdRendererOptions = {}) {
1463
1618
  properties["data-aimd-fig-src"] = figNode.src
1464
1619
  }
1465
1620
 
1621
+ if (node.fieldType === "cite") {
1622
+ const refs = "refs" in node ? (node as any).refs : [id]
1623
+ properties["data-aimd-refs"] = refs.join(",")
1624
+ }
1625
+
1626
+ if (node.fieldType === "refs") {
1627
+ properties.id = "refs"
1628
+ }
1629
+
1466
1630
  // Add quiz metadata for fallback reconstruction
1467
1631
  if (node.fieldType === "quiz") {
1468
1632
  const quizNode = node as AimdQuizNode
@@ -1474,8 +1638,8 @@ function createAimdHandler(options: AimdRendererOptions = {}) {
1474
1638
  const element: Element = assignAimdNodeData({
1475
1639
  type: "element",
1476
1640
  tagName: isRef
1477
- ? "a"
1478
- : (isFig ? "figure" : ((fieldType === "var_table" || isQuiz || isGroupedStepContainer) ? "div" : "span")),
1641
+ ? "span"
1642
+ : (isFig ? "figure" : (isRefs ? "section" : ((fieldType === "var_table" || isQuiz || isGroupedStepContainer) ? "div" : "span"))),
1479
1643
  properties,
1480
1644
  children,
1481
1645
  }, node)
@@ -1578,6 +1742,7 @@ export async function renderToHtml(
1578
1742
  const fields = getExtractedFields(file)
1579
1743
  annotateStepReferenceSequence(hastTree, fields, options)
1580
1744
  assignFigureSequenceNumbers(hastTree, fields)
1745
+ annotateCitationReferenceLabels(hastTree, fields)
1581
1746
  liftBlockVarElements(hastTree, options.blockVarTypes)
1582
1747
  if (options.groupStepBodies) {
1583
1748
  groupStepBodies(hastTree)
@@ -1586,6 +1751,7 @@ export async function renderToHtml(
1586
1751
  groupCheckBodies(hastTree)
1587
1752
  }
1588
1753
  await highlightVisibleAssigners(hastTree, options)
1754
+ moveReferenceSectionsToEnd(hastTree)
1589
1755
  const html = toHtml(hastTree, { allowDangerousHtml: true })
1590
1756
 
1591
1757
  return { html, fields }
@@ -1608,6 +1774,7 @@ export async function renderToVue(
1608
1774
  const fields = getExtractedFields(file)
1609
1775
  annotateStepReferenceSequence(hastTree, fields, options)
1610
1776
  assignFigureSequenceNumbers(hastTree, fields)
1777
+ annotateCitationReferenceLabels(hastTree, fields)
1611
1778
  liftBlockVarElements(hastTree, options.blockVarTypes)
1612
1779
  if (options.groupStepBodies) {
1613
1780
  groupStepBodies(hastTree)
@@ -1616,6 +1783,7 @@ export async function renderToVue(
1616
1783
  groupCheckBodies(hastTree)
1617
1784
  }
1618
1785
  await highlightVisibleAssigners(hastTree, options)
1786
+ moveReferenceSectionsToEnd(hastTree)
1619
1787
  const nodes = renderToVNodes(hastTree, options)
1620
1788
 
1621
1789
  return { nodes, fields }
@@ -1653,10 +1821,12 @@ export function renderToHtmlSync(
1653
1821
  const fields = getExtractedFields(file)
1654
1822
  annotateStepReferenceSequence(hastTree, fields, options)
1655
1823
  assignFigureSequenceNumbers(hastTree, fields)
1824
+ annotateCitationReferenceLabels(hastTree, fields)
1656
1825
  liftBlockVarElements(hastTree, options.blockVarTypes)
1657
1826
  if (options.groupStepBodies) {
1658
1827
  groupStepBodies(hastTree)
1659
1828
  }
1829
+ moveReferenceSectionsToEnd(hastTree)
1660
1830
  const html = toHtml(hastTree, { allowDangerousHtml: true })
1661
1831
 
1662
1832
  return { html, fields }
package/src/html/index.ts CHANGED
@@ -25,6 +25,10 @@ export type {
25
25
  AimdRendererOptions,
26
26
  RenderResult,
27
27
  } from '../common/processor'
28
+ export type {
29
+ AimdAssetUrlResolver,
30
+ AimdAssetUrlResolveContext,
31
+ } from '../common/assetUrls'
28
32
  export type {
29
33
  AimdRendererI18nOptions,
30
34
  AimdRendererLocale,
package/src/index.ts CHANGED
@@ -23,6 +23,11 @@ export {
23
23
  resolveAimdRendererLocale,
24
24
  } from './locales'
25
25
 
26
+ export type {
27
+ AimdAssetUrlResolver,
28
+ AimdAssetUrlResolveContext,
29
+ } from './common/assetUrls'
30
+
26
31
  export {
27
32
  bubbleMenuEventKey,
28
33
  draftEventKey,
package/src/locales.ts CHANGED
@@ -43,6 +43,9 @@ export interface AimdRendererMessages {
43
43
  reference: (value: string | number) => string
44
44
  captionTitle: (sequence: number, title?: string) => string
45
45
  }
46
+ references: {
47
+ title: string
48
+ }
46
49
  assigner: {
47
50
  clientSummary: string
48
51
  serverSummary: string
@@ -104,6 +107,9 @@ const EN_US_MESSAGES: AimdRendererMessages = {
104
107
  reference: value => `figure ${value}`,
105
108
  captionTitle: (sequence, title) => title ? `figure ${sequence}: ${title}` : `figure ${sequence}`,
106
109
  },
110
+ references: {
111
+ title: "References",
112
+ },
107
113
  assigner: {
108
114
  clientSummary: "Client assigner",
109
115
  serverSummary: "Server assigner",
@@ -141,6 +147,9 @@ const ZH_CN_MESSAGES: AimdRendererMessages = {
141
147
  reference: value => `图 ${value}`,
142
148
  captionTitle: (sequence, title) => title ? `图 ${sequence}:${title}` : `图 ${sequence}`,
143
149
  },
150
+ references: {
151
+ title: "参考文献",
152
+ },
144
153
  assigner: {
145
154
  clientSummary: "前端 assigner",
146
155
  serverSummary: "服务端 assigner",