@airalogy/aimd-renderer 2.7.0 → 2.8.0
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.
- package/dist/aimd-renderer.css +1 -1
- package/dist/common/assetUrls.d.ts +8 -0
- package/dist/common/assetUrls.d.ts.map +1 -0
- package/dist/common/citationNumbering.d.ts +11 -0
- package/dist/common/citationNumbering.d.ts.map +1 -0
- package/dist/common/processor.d.ts +2 -0
- package/dist/common/processor.d.ts.map +1 -1
- package/dist/html/index.d.ts +1 -0
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/locales.d.ts +3 -0
- package/dist/locales.d.ts.map +1 -1
- package/dist/{processor-BOCQYqXE.js → processor-C_zN3-hL.js} +3128 -2534
- package/dist/{readonly-record-renderer-CkzY7UvT.js → readonly-record-renderer-G9xOkOFe.js} +1 -1
- package/dist/vue/index.d.ts +1 -0
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/vue-renderer.d.ts +7 -1
- package/dist/vue/vue-renderer.d.ts.map +1 -1
- package/dist/vue.js +2 -2
- package/package.json +2 -2
- package/src/__tests__/renderer.test.ts +292 -0
- package/src/common/assetUrls.ts +22 -0
- package/src/common/citationNumbering.ts +265 -0
- package/src/common/processor.ts +193 -23
- package/src/html/index.ts +4 -0
- package/src/index.ts +5 -0
- package/src/locales.ts +9 -0
- package/src/styles/katex.css +148 -0
- package/src/vue/index.ts +4 -0
- package/src/vue/vue-renderer.ts +183 -9
|
@@ -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
|
+
}
|
package/src/common/processor.ts
CHANGED
|
@@ -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:
|
|
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: "
|
|
1099
|
-
properties: { className: ["aimd-
|
|
1100
|
-
children:
|
|
1243
|
+
tagName: "div",
|
|
1244
|
+
properties: { className: ["aimd-refs__content"] },
|
|
1245
|
+
children: buildReferencesChildren(entries, messages.references.title),
|
|
1101
1246
|
} as Element)
|
|
1102
1247
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
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
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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:
|
|
1363
|
-
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
|
|
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
|
|
1553
|
+
properties["data-aimd-ref-kind"] = "step"
|
|
1399
1554
|
}
|
|
1400
1555
|
else if (fieldType === "ref_var") {
|
|
1401
|
-
properties
|
|
1556
|
+
properties["data-aimd-ref-kind"] = "var"
|
|
1402
1557
|
}
|
|
1403
1558
|
else if (fieldType === "ref_fig") {
|
|
1404
|
-
properties
|
|
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
|
-
? "
|
|
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
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",
|