@airalogy/aimd-renderer 2.6.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/README.md +38 -0
- package/README.zh-CN.md +38 -0
- 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/criticMarkup.d.ts +10 -0
- package/dist/common/criticMarkup.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 +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +80 -73
- package/dist/locales.d.ts +3 -0
- package/dist/locales.d.ts.map +1 -1
- package/dist/{processor-CHbNEcN8.js → processor-C_zN3-hL.js} +3454 -2534
- package/dist/readonly-record-renderer-G9xOkOFe.js +711 -0
- package/dist/vue/index.d.ts +2 -0
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/readonly-record-renderer.d.ts +42 -0
- package/dist/vue/readonly-record-renderer.d.ts.map +1 -0
- package/dist/vue/vue-renderer.d.ts +7 -1
- package/dist/vue/vue-renderer.d.ts.map +1 -1
- package/dist/vue.js +20 -13
- package/package.json +2 -2
- package/src/__tests__/renderer.test.ts +536 -1
- package/src/common/assetUrls.ts +22 -0
- package/src/common/citationNumbering.ts +265 -0
- package/src/common/criticMarkup.ts +97 -0
- package/src/common/processor.ts +206 -25
- package/src/html/index.ts +4 -0
- package/src/index.ts +23 -0
- package/src/locales.ts +9 -0
- package/src/styles/katex.css +213 -0
- package/src/vue/index.ts +22 -0
- package/src/vue/readonly-record-renderer.ts +747 -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
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Element, Text as HastText } from "hast"
|
|
2
|
+
import type {
|
|
3
|
+
AimdCriticMarkupBaseNode,
|
|
4
|
+
AimdCriticMarkupChild,
|
|
5
|
+
AimdCriticMarkupNode,
|
|
6
|
+
AimdCriticSubstitutionNode,
|
|
7
|
+
} from "@airalogy/aimd-core/types"
|
|
8
|
+
|
|
9
|
+
function createTextHast(value: string): HastText {
|
|
10
|
+
return { type: "text", value }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isCriticMarkupNode(node: AimdCriticMarkupChild): node is AimdCriticMarkupNode {
|
|
14
|
+
return typeof node.type === "string" && node.type.startsWith("critic")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function renderInlineChildren(state: any, children: AimdCriticMarkupChild[] = []): Array<Element | HastText> {
|
|
18
|
+
return children.flatMap((child) => {
|
|
19
|
+
if (child.type === "text") {
|
|
20
|
+
return [createTextHast(child.value ?? "")] as Array<Element | HastText>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (isCriticMarkupNode(child)) {
|
|
24
|
+
const handler = criticMarkupHandlers[child.type as keyof typeof criticMarkupHandlers] as
|
|
25
|
+
| ((state: any, node: any) => Element)
|
|
26
|
+
| undefined
|
|
27
|
+
if (handler) {
|
|
28
|
+
return [handler(state, child)]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const rendered = typeof state.one === "function" ? state.one(child, undefined) : null
|
|
33
|
+
return rendered ? [rendered as Element | HastText] : []
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createCriticElement(
|
|
38
|
+
tagName: string,
|
|
39
|
+
kind: string,
|
|
40
|
+
children: Array<Element | HastText>,
|
|
41
|
+
extraClassNames: string[] = [],
|
|
42
|
+
): Element {
|
|
43
|
+
return {
|
|
44
|
+
type: "element",
|
|
45
|
+
tagName,
|
|
46
|
+
properties: {
|
|
47
|
+
className: ["aimd-critic", `aimd-critic--${kind}`, ...extraClassNames],
|
|
48
|
+
"data-critic-kind": kind,
|
|
49
|
+
},
|
|
50
|
+
children,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const criticMarkupHandlers = {
|
|
55
|
+
criticAddition(state: any, node: AimdCriticMarkupBaseNode): Element {
|
|
56
|
+
return createCriticElement("ins", "addition", renderInlineChildren(state, node.children))
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
criticDeletion(state: any, node: AimdCriticMarkupBaseNode): Element {
|
|
60
|
+
return createCriticElement("del", "deletion", renderInlineChildren(state, node.children))
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
criticHighlight(state: any, node: AimdCriticMarkupBaseNode): Element {
|
|
64
|
+
return createCriticElement("mark", "highlight", renderInlineChildren(state, node.children))
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
criticComment(_state: any, node: AimdCriticMarkupBaseNode): Element {
|
|
68
|
+
const value = node.value.trim()
|
|
69
|
+
return createCriticElement("span", "comment", [
|
|
70
|
+
{
|
|
71
|
+
type: "element",
|
|
72
|
+
tagName: "span",
|
|
73
|
+
properties: { className: ["aimd-critic__comment-label"] },
|
|
74
|
+
children: [createTextHast("Comment")],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: "element",
|
|
78
|
+
tagName: "span",
|
|
79
|
+
properties: { className: ["aimd-critic__comment-body"] },
|
|
80
|
+
children: [createTextHast(value)],
|
|
81
|
+
},
|
|
82
|
+
], value ? [] : ["aimd-critic--empty-comment"])
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
criticSubstitution(state: any, node: AimdCriticSubstitutionNode): Element {
|
|
86
|
+
return createCriticElement("span", "substitution", [
|
|
87
|
+
createCriticElement("del", "deletion", renderInlineChildren(state, node.oldChildren), ["aimd-critic--substitution-old"]),
|
|
88
|
+
{
|
|
89
|
+
type: "element",
|
|
90
|
+
tagName: "span",
|
|
91
|
+
properties: { className: ["aimd-critic__replacement-arrow"], "aria-hidden": "true" },
|
|
92
|
+
children: [createTextHast("->")],
|
|
93
|
+
},
|
|
94
|
+
createCriticElement("ins", "addition", renderInlineChildren(state, node.newChildren), ["aimd-critic--substitution-new"]),
|
|
95
|
+
])
|
|
96
|
+
},
|
|
97
|
+
}
|