@airalogy/aimd-renderer 2.6.0 → 2.7.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/criticMarkup.d.ts +10 -0
- package/dist/common/criticMarkup.d.ts.map +1 -0
- package/dist/common/processor.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 +80 -73
- package/dist/{processor-CHbNEcN8.js → processor-BOCQYqXE.js} +1899 -1573
- package/dist/readonly-record-renderer-CkzY7UvT.js +711 -0
- package/dist/vue/index.d.ts +1 -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.js +20 -13
- package/package.json +2 -2
- package/src/__tests__/renderer.test.ts +244 -1
- package/src/common/criticMarkup.ts +97 -0
- package/src/common/processor.ts +13 -2
- package/src/index.ts +18 -0
- package/src/styles/katex.css +65 -0
- package/src/vue/index.ts +18 -0
- package/src/vue/readonly-record-renderer.ts +747 -0
package/dist/vue/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Vue rendering exports
|
|
3
3
|
*/
|
|
4
4
|
export { type AimdComponentRenderer, type AimdRendererContext, type AssetResolver, createAssetRenderer, createCodeBlockRenderer, createComponentRenderer, createEmbeddedRenderer, createMermaidRenderer, createStepCardRenderer, loadShikiHighlighter, type CodeBlockRendererOptions, type ElementRenderer, hastToVue, renderToVNodes, type LoadShikiHighlighterOptions, type AimdStepCardRendererOptions, type ShikiHighlighter, type VueRendererOptions, } from './vue-renderer';
|
|
5
|
+
export { AIMD_RECORD_RENDER_SCOPES, createReadonlyRecordAimdRenderers, createReadonlyRecordElementRenderers, createReadonlyRecordRenderContext, normalizeRecordRenderValue, renderReadonlyRecordToVue, type AimdRecordRenderScope, type AimdRecordRenderValue, type ReadonlyRecordAsset, type ReadonlyRecordAssetKind, type ReadonlyRecordAssetResolveContext, type ReadonlyRecordAssetResolver, type ReadonlyRecordMarkdownRenderOptions, type ReadonlyRecordRenderContextInput, type ReadonlyRecordVueRendererOptions, } from './readonly-record-renderer';
|
|
5
6
|
export { renderToVue, createRenderer, defaultRenderer, } from '../common/processor';
|
|
6
7
|
export { createAimdRendererMessages, DEFAULT_AIMD_RENDERER_LOCALE, resolveAimdRendererLocale, } from '../locales';
|
|
7
8
|
export type { RenderContext, RenderMode, ProcessorOptions, } from '@airalogy/aimd-core/types';
|
package/dist/vue/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vue/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,EACpB,KAAK,wBAAwB,EAC7B,KAAK,eAAe,EACpB,SAAS,EACT,cAAc,EACd,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,EAChC,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACL,WAAW,EACX,cAAc,EACd,eAAe,GAChB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EACL,0BAA0B,EAC1B,4BAA4B,EAC5B,yBAAyB,GAC1B,MAAM,YAAY,CAAA;AAEnB,YAAY,EACV,aAAa,EACb,UAAU,EACV,gBAAgB,GACjB,MAAM,2BAA2B,CAAA;AAElC,YAAY,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACpG,YAAY,EACV,uBAAuB,EACvB,kBAAkB,EAClB,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vue/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,EACpB,KAAK,wBAAwB,EAC7B,KAAK,eAAe,EACpB,SAAS,EACT,cAAc,EACd,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,EAChC,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACL,yBAAyB,EACzB,iCAAiC,EACjC,oCAAoC,EACpC,iCAAiC,EACjC,0BAA0B,EAC1B,yBAAyB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,iCAAiC,EACtC,KAAK,2BAA2B,EAChC,KAAK,mCAAmC,EACxC,KAAK,gCAAgC,EACrC,KAAK,gCAAgC,GACtC,MAAM,4BAA4B,CAAA;AAEnC,OAAO,EACL,WAAW,EACX,cAAc,EACd,eAAe,GAChB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EACL,0BAA0B,EAC1B,4BAA4B,EAC5B,yBAAyB,GAC1B,MAAM,YAAY,CAAA;AAEnB,YAAY,EACV,aAAa,EACb,UAAU,EACV,gBAAgB,GACjB,MAAM,2BAA2B,CAAA;AAElC,YAAY,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACpG,YAAY,EACV,uBAAuB,EACvB,kBAAkB,EAClB,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AimdNode, RenderContext } from '@airalogy/aimd-core/types';
|
|
2
|
+
import { type AimdAssetKind, type AimdAssetLike, type AimdRecordDataScope, type AimdRecordDataValue } from '@airalogy/aimd-core/utils';
|
|
3
|
+
import type { AimdRendererOptions, RenderResult } from '../common/processor';
|
|
4
|
+
import type { AimdComponentRenderer, AimdRendererContext, ElementRenderer, VueRendererOptions } from './vue-renderer';
|
|
5
|
+
export declare const AIMD_RECORD_RENDER_SCOPES: readonly ["var", "var_table", "step", "check", "quiz"];
|
|
6
|
+
export type AimdRecordRenderScope = AimdRecordDataScope;
|
|
7
|
+
export type ReadonlyRecordAssetKind = AimdAssetKind;
|
|
8
|
+
export type AimdRecordRenderValue = AimdRecordDataValue;
|
|
9
|
+
export interface ReadonlyRecordAsset extends AimdAssetLike {
|
|
10
|
+
url?: string;
|
|
11
|
+
href?: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
filename?: string;
|
|
14
|
+
mimeType?: string;
|
|
15
|
+
size?: number;
|
|
16
|
+
kind?: ReadonlyRecordAssetKind;
|
|
17
|
+
downloadName?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ReadonlyRecordAssetResolveContext {
|
|
20
|
+
fieldId: string;
|
|
21
|
+
fieldPath: string;
|
|
22
|
+
scope: string;
|
|
23
|
+
node: AimdNode;
|
|
24
|
+
value: unknown;
|
|
25
|
+
normalizedValue: unknown;
|
|
26
|
+
fileId?: string;
|
|
27
|
+
recordValue: RenderContext['value'];
|
|
28
|
+
}
|
|
29
|
+
export type ReadonlyRecordAssetResolver = (context: ReadonlyRecordAssetResolveContext) => ReadonlyRecordAsset | null | undefined;
|
|
30
|
+
export type ReadonlyRecordRenderContextInput = Partial<RenderContext> & Partial<Pick<AimdRendererContext, 'locale' | 'messages'>>;
|
|
31
|
+
export interface ReadonlyRecordVueRendererOptions extends AimdRendererOptions, VueRendererOptions {
|
|
32
|
+
resolveAsset?: ReadonlyRecordAssetResolver;
|
|
33
|
+
}
|
|
34
|
+
export type ReadonlyRecordMarkdownRenderOptions = AimdRendererOptions & Pick<VueRendererOptions, 'componentMap' | 'elementRenderers' | 'locale' | 'messages' | 'quizPreview'>;
|
|
35
|
+
export declare function createReadonlyRecordAimdRenderers(options?: Pick<ReadonlyRecordVueRendererOptions, 'resolveAsset'> & {
|
|
36
|
+
markdownRenderOptions?: ReadonlyRecordMarkdownRenderOptions;
|
|
37
|
+
}): Record<string, AimdComponentRenderer>;
|
|
38
|
+
export declare function createReadonlyRecordElementRenderers(options?: Pick<ReadonlyRecordVueRendererOptions, 'resolveAsset'>): Record<string, ElementRenderer>;
|
|
39
|
+
export declare function normalizeRecordRenderValue(recordData: unknown): RenderContext['value'];
|
|
40
|
+
export declare function createReadonlyRecordRenderContext<T extends ReadonlyRecordRenderContextInput>(recordData: unknown, context?: T): T & RenderContext;
|
|
41
|
+
export declare function renderReadonlyRecordToVue(content: string, recordData: unknown, options?: ReadonlyRecordVueRendererOptions): Promise<RenderResult>;
|
|
42
|
+
//# sourceMappingURL=readonly-record-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readonly-record-renderer.d.ts","sourceRoot":"","sources":["../../src/vue/readonly-record-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,QAAQ,EAIR,aAAa,EACd,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAqBL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAA;AAGlC,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAE5E,OAAO,KAAK,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAErH,eAAO,MAAM,yBAAyB,wDAA0B,CAAA;AAEhE,MAAM,MAAM,qBAAqB,GAAG,mBAAmB,CAAA;AAEvD,MAAM,MAAM,uBAAuB,GAAG,aAAa,CAAA;AAEnD,MAAM,MAAM,qBAAqB,GAAG,mBAAmB,CAAA;AAEvD,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACxD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,uBAAuB,CAAA;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,iCAAiC;IAChD,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,QAAQ,CAAA;IACd,KAAK,EAAE,OAAO,CAAA;IACd,eAAe,EAAE,OAAO,CAAA;IACxB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;CACpC;AAED,MAAM,MAAM,2BAA2B,GAAG,CACxC,OAAO,EAAE,iCAAiC,KACvC,mBAAmB,GAAG,IAAI,GAAG,SAAS,CAAA;AAE3C,MAAM,MAAM,gCAAgC,GAAG,OAAO,CAAC,aAAa,CAAC,GACjE,OAAO,CAAC,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAA;AAE7D,MAAM,WAAW,gCAAiC,SAAQ,mBAAmB,EAAE,kBAAkB;IAC/F,YAAY,CAAC,EAAE,2BAA2B,CAAA;CAC3C;AAED,MAAM,MAAM,mCAAmC,GAAG,mBAAmB,GACjE,IAAI,CAAC,kBAAkB,EAAE,cAAc,GAAG,kBAAkB,GAAG,QAAQ,GAAG,UAAU,GAAG,aAAa,CAAC,CAAA;AAulBzG,wBAAgB,iCAAiC,CAC/C,OAAO,GAAE,IAAI,CAAC,gCAAgC,EAAE,cAAc,CAAC,GAAG;IAChE,qBAAqB,CAAC,EAAE,mCAAmC,CAAA;CACvD,GACL,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAQvC;AAED,wBAAgB,oCAAoC,CAClD,OAAO,GAAE,IAAI,CAAC,gCAAgC,EAAE,cAAc,CAAM,GACnE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAIjC;AAED,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC,CAEtF;AAED,wBAAgB,iCAAiC,CAAC,CAAC,SAAS,gCAAgC,EAC1F,UAAU,EAAE,OAAO,EACnB,OAAO,CAAC,EAAE,CAAC,GACV,CAAC,GAAG,aAAa,CAOnB;AAED,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,OAAO,EACnB,OAAO,GAAE,gCAAqC,GAC7C,OAAO,CAAC,YAAY,CAAC,CAsBvB"}
|
package/dist/vue.js
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
import { D as a, c as
|
|
1
|
+
import { D as a, c as d, f as s, g as R, h as n, i as o, j as t, k as c, l, m, n as E, o as i, q as A, s as C, e as D } from "./processor-BOCQYqXE.js";
|
|
2
|
+
import { A as h, c as u, a as T, b as V, n as f, r as g } from "./readonly-record-renderer-CkzY7UvT.js";
|
|
2
3
|
export {
|
|
4
|
+
h as AIMD_RECORD_RENDER_SCOPES,
|
|
3
5
|
a as DEFAULT_AIMD_RENDERER_LOCALE,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
d as createAimdRendererMessages,
|
|
7
|
+
s as createAssetRenderer,
|
|
8
|
+
R as createCodeBlockRenderer,
|
|
9
|
+
n as createComponentRenderer,
|
|
8
10
|
o as createEmbeddedRenderer,
|
|
9
|
-
|
|
11
|
+
t as createMermaidRenderer,
|
|
12
|
+
u as createReadonlyRecordAimdRenderers,
|
|
13
|
+
T as createReadonlyRecordElementRenderers,
|
|
14
|
+
V as createReadonlyRecordRenderContext,
|
|
10
15
|
c as createRenderer,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
l as createStepCardRenderer,
|
|
17
|
+
m as defaultRenderer,
|
|
18
|
+
E as hastToVue,
|
|
19
|
+
i as loadShikiHighlighter,
|
|
20
|
+
f as normalizeRecordRenderValue,
|
|
21
|
+
g as renderReadonlyRecordToVue,
|
|
22
|
+
A as renderToVNodes,
|
|
23
|
+
C as renderToVue,
|
|
24
|
+
D as resolveAimdRendererLocale
|
|
18
25
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@airalogy/aimd-renderer",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.7.0",
|
|
5
5
|
"description": "AIMD (Airalogy Markdown) rendering engines for HTML and Vue",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"shiki": "^2.5.0",
|
|
65
65
|
"unified": "^11.0.5",
|
|
66
66
|
"vfile": "^6.0.3",
|
|
67
|
-
"@airalogy/aimd-core": "^2.
|
|
67
|
+
"@airalogy/aimd-core": "^2.9.0"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@types/node": "^24.3.0",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mount } from '@vue/test-utils'
|
|
2
|
+
import { h } from 'vue'
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
4
|
|
|
3
5
|
import { resolveQuizPreviewOptions } from '../common/quiz-preview'
|
|
4
6
|
import {
|
|
@@ -9,6 +11,11 @@ import {
|
|
|
9
11
|
createRenderer,
|
|
10
12
|
} from '../common/processor'
|
|
11
13
|
import { getFinalIndent, parseFieldTag } from '../index'
|
|
14
|
+
import {
|
|
15
|
+
createReadonlyRecordRenderContext,
|
|
16
|
+
normalizeRecordRenderValue,
|
|
17
|
+
renderReadonlyRecordToVue,
|
|
18
|
+
} from '../vue/readonly-record-renderer'
|
|
12
19
|
import { createCodeBlockRenderer, createStepCardRenderer } from '../vue/vue-renderer'
|
|
13
20
|
|
|
14
21
|
function findVNodeByType(node: any, expectedType: string): any | null {
|
|
@@ -194,6 +201,36 @@ describe('renderToHtmlSync', () => {
|
|
|
194
201
|
expect(html).toContain('Hello')
|
|
195
202
|
})
|
|
196
203
|
|
|
204
|
+
it('renders CriticMarkup review marks to semantic HTML', () => {
|
|
205
|
+
const { html } = renderToHtmlSync('Add {++new++}, delete {--old--}, replace {~~old~>new~~}, comment {>>check units<<}, highlight {==important==}.')
|
|
206
|
+
|
|
207
|
+
expect(html).toContain('<ins class="aimd-critic aimd-critic--addition"')
|
|
208
|
+
expect(html).toContain('data-critic-kind="addition"')
|
|
209
|
+
expect(html).toContain('<del class="aimd-critic aimd-critic--deletion"')
|
|
210
|
+
expect(html).toContain('data-critic-kind="deletion"')
|
|
211
|
+
expect(html).toContain('<span class="aimd-critic aimd-critic--substitution"')
|
|
212
|
+
expect(html).toContain('aimd-critic--substitution-old')
|
|
213
|
+
expect(html).toContain('aimd-critic--substitution-new')
|
|
214
|
+
expect(html).toContain('<span class="aimd-critic aimd-critic--comment"')
|
|
215
|
+
expect(html).toContain('check units')
|
|
216
|
+
expect(html).toContain('<mark class="aimd-critic aimd-critic--highlight"')
|
|
217
|
+
expect(html).toContain('important')
|
|
218
|
+
expect(html).not.toContain('{++new++}')
|
|
219
|
+
expect(html).not.toContain('{--old--}')
|
|
220
|
+
expect(html).not.toContain('{~~old~>new~~}')
|
|
221
|
+
expect(html).not.toContain('{>>check units<<}')
|
|
222
|
+
expect(html).not.toContain('{==important==}')
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('does not parse CriticMarkup inside inline code', () => {
|
|
226
|
+
const { html } = renderToHtmlSync('Literal `{++not a mark++}` and `{~~old~>new~~}` remain code.')
|
|
227
|
+
|
|
228
|
+
expect(html).toContain('<code>{++not a mark++}</code>')
|
|
229
|
+
expect(html).toContain('<code>{~~old~>new~~}</code>')
|
|
230
|
+
expect(html).not.toContain('aimd-critic--addition')
|
|
231
|
+
expect(html).not.toContain('aimd-critic--substitution')
|
|
232
|
+
})
|
|
233
|
+
|
|
197
234
|
it('renders AIMD var fields', () => {
|
|
198
235
|
const { html, fields } = renderToHtmlSync('{{var|temperature}}')
|
|
199
236
|
expect(fields.var).toContain('temperature')
|
|
@@ -543,6 +580,212 @@ describe('renderToVue', () => {
|
|
|
543
580
|
})
|
|
544
581
|
})
|
|
545
582
|
|
|
583
|
+
// ---------------------------------------------------------------------------
|
|
584
|
+
// readonly record rendering
|
|
585
|
+
// ---------------------------------------------------------------------------
|
|
586
|
+
|
|
587
|
+
describe('readonly record rendering', () => {
|
|
588
|
+
it('normalizes bare record data and full record payload wrappers', () => {
|
|
589
|
+
expect(normalizeRecordRenderValue({
|
|
590
|
+
var: { sample_id: { value: 'S-001' } },
|
|
591
|
+
step: { prepare: { checked: true } },
|
|
592
|
+
var_table: {
|
|
593
|
+
samples: [{ sample_id: 'S-001' }],
|
|
594
|
+
},
|
|
595
|
+
})).toEqual({
|
|
596
|
+
var: { sample_id: { value: 'S-001' } },
|
|
597
|
+
step: { prepare: { checked: true } },
|
|
598
|
+
check: {},
|
|
599
|
+
quiz: {},
|
|
600
|
+
var_table: {
|
|
601
|
+
samples: [{ sample_id: 'S-001' }],
|
|
602
|
+
},
|
|
603
|
+
})
|
|
604
|
+
|
|
605
|
+
expect(normalizeRecordRenderValue({
|
|
606
|
+
record_id: 'rec-001',
|
|
607
|
+
data: {
|
|
608
|
+
var: { sample_id: 'S-002' },
|
|
609
|
+
quiz: { qc: ['A', 'B'] },
|
|
610
|
+
},
|
|
611
|
+
})).toEqual({
|
|
612
|
+
var: { sample_id: 'S-002' },
|
|
613
|
+
var_table: {},
|
|
614
|
+
step: {},
|
|
615
|
+
check: {},
|
|
616
|
+
quiz: { qc: ['A', 'B'] },
|
|
617
|
+
})
|
|
618
|
+
})
|
|
619
|
+
|
|
620
|
+
it('creates an edit-mode readonly render context for record-backed documents', () => {
|
|
621
|
+
const context = createReadonlyRecordRenderContext(
|
|
622
|
+
{ data: { var: { sample_id: 'S-003' } } },
|
|
623
|
+
{ quizPreview: { showAnswers: true } },
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
expect(context.mode).toBe('edit')
|
|
627
|
+
expect(context.readonly).toBe(true)
|
|
628
|
+
expect(context.quizPreview).toEqual({ showAnswers: true })
|
|
629
|
+
expect(context.value?.var.sample_id).toBe('S-003')
|
|
630
|
+
})
|
|
631
|
+
|
|
632
|
+
it('renders record values into AIMD fields without enabling input editing', async () => {
|
|
633
|
+
const { nodes } = await renderReadonlyRecordToVue(
|
|
634
|
+
[
|
|
635
|
+
'Sample {{var|sample_id: str}}',
|
|
636
|
+
'',
|
|
637
|
+
'{{check|prepared}} Prepared.',
|
|
638
|
+
].join('\n'),
|
|
639
|
+
{
|
|
640
|
+
data: {
|
|
641
|
+
var: { sample_id: { value: 'S-004' } },
|
|
642
|
+
check: { prepared: { checked: true } },
|
|
643
|
+
},
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
groupCheckBodies: true,
|
|
647
|
+
},
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
expect(collectVNodeText(nodes)).toContain('S-004')
|
|
651
|
+
expect(collectVNodeText(nodes)).not.toContain('sample_id')
|
|
652
|
+
const input = findVNodeByType({ children: nodes }, 'input') as any
|
|
653
|
+
expect(input).toBeTruthy()
|
|
654
|
+
expect(input.props.checked).toBe(true)
|
|
655
|
+
expect(input.props.disabled).toBe(true)
|
|
656
|
+
})
|
|
657
|
+
|
|
658
|
+
it('shows readable missing labels only when record values are absent', async () => {
|
|
659
|
+
const { nodes } = await renderReadonlyRecordToVue(
|
|
660
|
+
'Sample {{var|sample_id: str, title="Sample ID"}}',
|
|
661
|
+
{
|
|
662
|
+
data: {
|
|
663
|
+
var: {},
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
expect(collectVNodeText(nodes)).toContain('Missing')
|
|
669
|
+
expect(collectVNodeText(nodes)).toContain('Sample ID')
|
|
670
|
+
const missing = findVNodeByType({ children: nodes }, 'span') as any
|
|
671
|
+
expect(missing.props.title).toContain('data.var.sample_id')
|
|
672
|
+
})
|
|
673
|
+
|
|
674
|
+
it('renders file-backed image fields with resolved assets', async () => {
|
|
675
|
+
const { nodes } = await renderReadonlyRecordToVue(
|
|
676
|
+
'Site photo: {{var|site_photo: FileIdPNG}}',
|
|
677
|
+
{
|
|
678
|
+
data: {
|
|
679
|
+
var: {
|
|
680
|
+
site_photo: 'airalogy.id.file.site-photo.png',
|
|
681
|
+
},
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
resolveAsset: context => context.fileId === 'airalogy.id.file.site-photo.png'
|
|
686
|
+
? {
|
|
687
|
+
url: 'blob:site-photo',
|
|
688
|
+
filename: 'site-photo.png',
|
|
689
|
+
mimeType: 'image/png',
|
|
690
|
+
}
|
|
691
|
+
: null,
|
|
692
|
+
},
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
const img = findVNodeByType({ children: nodes }, 'img') as any
|
|
696
|
+
expect(img).toBeTruthy()
|
|
697
|
+
expect(img.props.src).toBe('blob:site-photo')
|
|
698
|
+
expect(img.props.alt).toBe('site-photo.png')
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
it('renders manifest-resolved file fields even when the protocol did not declare FileId type', async () => {
|
|
702
|
+
const { nodes } = await renderReadonlyRecordToVue(
|
|
703
|
+
'Attachment: {{var|sample_file}}',
|
|
704
|
+
{
|
|
705
|
+
data: {
|
|
706
|
+
var: {
|
|
707
|
+
sample_file: 'airalogy.id.file.sample-note.txt',
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
resolveAsset: context => context.fieldPath === 'data.var.sample_file'
|
|
713
|
+
? {
|
|
714
|
+
href: 'blob:sample-note',
|
|
715
|
+
filename: 'sample-note.txt',
|
|
716
|
+
mimeType: 'text/plain',
|
|
717
|
+
}
|
|
718
|
+
: null,
|
|
719
|
+
},
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
const link = findVNodeByType({ children: nodes }, 'a') as any
|
|
723
|
+
expect(link).toBeTruthy()
|
|
724
|
+
expect(link.props.href).toBe('blob:sample-note')
|
|
725
|
+
expect(collectVNodeText(link)).toContain('sample-note.txt')
|
|
726
|
+
})
|
|
727
|
+
|
|
728
|
+
it('resolves markdown image elements through readonly record assets', async () => {
|
|
729
|
+
const { nodes } = await renderReadonlyRecordToVue(
|
|
730
|
+
'',
|
|
731
|
+
{ data: { var: {} } },
|
|
732
|
+
{
|
|
733
|
+
resolveAsset: context => context.fileId === 'airalogy.id.file.chart.svg'
|
|
734
|
+
? {
|
|
735
|
+
url: 'blob:chart',
|
|
736
|
+
filename: 'chart.svg',
|
|
737
|
+
mimeType: 'image/svg+xml',
|
|
738
|
+
}
|
|
739
|
+
: null,
|
|
740
|
+
},
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
const img = findVNodeByType({ children: nodes }, 'img') as any
|
|
744
|
+
expect(img).toBeTruthy()
|
|
745
|
+
expect(img.props.src).toBe('blob:chart')
|
|
746
|
+
expect(img.props.alt).toBe('Calibration chart')
|
|
747
|
+
})
|
|
748
|
+
|
|
749
|
+
it('renders AiralogyMarkdown record values through the AIMD Vue renderer', async () => {
|
|
750
|
+
const { nodes } = await renderReadonlyRecordToVue(
|
|
751
|
+
'Report:\n\n{{var|report: AiralogyMarkdown}}',
|
|
752
|
+
{
|
|
753
|
+
data: {
|
|
754
|
+
var: {
|
|
755
|
+
report: [
|
|
756
|
+
'# Finding',
|
|
757
|
+
'',
|
|
758
|
+
'',
|
|
759
|
+
'',
|
|
760
|
+
'Nested token: {{var|nested: str, title="Nested value"}}',
|
|
761
|
+
].join('\n'),
|
|
762
|
+
},
|
|
763
|
+
},
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
resolveAsset: context => context.fileId === 'airalogy.id.file.chart.svg'
|
|
767
|
+
? {
|
|
768
|
+
url: 'blob:chart',
|
|
769
|
+
filename: 'chart.svg',
|
|
770
|
+
mimeType: 'image/svg+xml',
|
|
771
|
+
}
|
|
772
|
+
: null,
|
|
773
|
+
},
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
const wrapper = mount({
|
|
777
|
+
render: () => h('div', nodes),
|
|
778
|
+
})
|
|
779
|
+
|
|
780
|
+
await vi.waitFor(() => {
|
|
781
|
+
expect(wrapper.find('.aimd-record-field--markdown h1').text()).toBe('Finding')
|
|
782
|
+
expect(wrapper.find('.aimd-record-field--markdown img').attributes('src')).toBe('blob:chart')
|
|
783
|
+
})
|
|
784
|
+
expect(wrapper.find('.aimd-record-field--markdown').text()).toContain('Nested value')
|
|
785
|
+
wrapper.unmount()
|
|
786
|
+
})
|
|
787
|
+
})
|
|
788
|
+
|
|
546
789
|
// ---------------------------------------------------------------------------
|
|
547
790
|
// createRenderer
|
|
548
791
|
// ---------------------------------------------------------------------------
|
|
@@ -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
|
+
}
|
package/src/common/processor.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { resolveQuizPreviewOptions } from "./quiz-preview"
|
|
|
21
21
|
import { remarkInsertVisibleAssigners, remarkStripAssignerCodeBlocks } from "./assignerVisibility"
|
|
22
22
|
import { highlightVisibleAssigners } from "./assignerHighlighting"
|
|
23
23
|
import { annotateStepReferenceSequence } from "./annotateStepReferences"
|
|
24
|
+
import { criticMarkupHandlers } from "./criticMarkup"
|
|
24
25
|
import { buildFigureChildren, assignFigureSequenceNumbers } from "./figureNumbering"
|
|
25
26
|
|
|
26
27
|
// ---------------------------------------------------------------------------
|
|
@@ -511,7 +512,13 @@ import remarkParse from "remark-parse"
|
|
|
511
512
|
import remarkRehype from "remark-rehype"
|
|
512
513
|
import { unified } from "unified"
|
|
513
514
|
|
|
514
|
-
import {
|
|
515
|
+
import {
|
|
516
|
+
CRITIC_MARKUP_SUBSTITUTIONS_DATA_KEY,
|
|
517
|
+
protectAimdInlineTemplates,
|
|
518
|
+
protectCriticMarkupSubstitutions,
|
|
519
|
+
remarkAimd,
|
|
520
|
+
remarkCriticMarkup,
|
|
521
|
+
} from "@airalogy/aimd-core/parser"
|
|
515
522
|
import {
|
|
516
523
|
formatAimdExampleValue,
|
|
517
524
|
formatAimdExamples,
|
|
@@ -567,14 +574,16 @@ async function ensureMathStylesLoaded(mathEnabled: boolean | undefined): Promise
|
|
|
567
574
|
|
|
568
575
|
function createAimdParseInput(content: string) {
|
|
569
576
|
const { content: protectedContent, templates } = protectAimdInlineTemplates(content)
|
|
577
|
+
const { content: criticProtectedContent, substitutions } = protectCriticMarkupSubstitutions(protectedContent)
|
|
570
578
|
const file: VFile = {
|
|
571
579
|
data: {
|
|
572
580
|
aimdInlineTemplates: templates,
|
|
581
|
+
[CRITIC_MARKUP_SUBSTITUTIONS_DATA_KEY]: substitutions,
|
|
573
582
|
},
|
|
574
583
|
} as unknown as VFile
|
|
575
584
|
|
|
576
585
|
return {
|
|
577
|
-
content:
|
|
586
|
+
content: criticProtectedContent,
|
|
578
587
|
file,
|
|
579
588
|
}
|
|
580
589
|
}
|
|
@@ -1512,6 +1521,7 @@ function createBaseProcessor(options: AimdRendererOptions = {}) {
|
|
|
1512
1521
|
// to properly parse multiline AIMD syntax like var_table with subvars
|
|
1513
1522
|
processor.use(remarkAimd)
|
|
1514
1523
|
processor.use(remarkStripAssignerCodeBlocks)
|
|
1524
|
+
processor.use(remarkCriticMarkup)
|
|
1515
1525
|
|
|
1516
1526
|
// Single line break to <br> conversion (default enabled for AIMD)
|
|
1517
1527
|
if (breaks) {
|
|
@@ -1534,6 +1544,7 @@ export function createHtmlProcessor(options: AimdRendererOptions = {}) {
|
|
|
1534
1544
|
handlers: {
|
|
1535
1545
|
// Custom handler for AIMD nodes
|
|
1536
1546
|
aimd: aimdHandler,
|
|
1547
|
+
...criticMarkupHandlers,
|
|
1537
1548
|
},
|
|
1538
1549
|
} as any)
|
|
1539
1550
|
.use(rehypeRaw)
|
package/src/index.ts
CHANGED
|
@@ -41,6 +41,24 @@ export {
|
|
|
41
41
|
} from './common/unified-token-renderer'
|
|
42
42
|
|
|
43
43
|
// Vue renderer exports
|
|
44
|
+
export {
|
|
45
|
+
AIMD_RECORD_RENDER_SCOPES,
|
|
46
|
+
createReadonlyRecordAimdRenderers,
|
|
47
|
+
createReadonlyRecordElementRenderers,
|
|
48
|
+
createReadonlyRecordRenderContext,
|
|
49
|
+
normalizeRecordRenderValue,
|
|
50
|
+
renderReadonlyRecordToVue,
|
|
51
|
+
type AimdRecordRenderScope,
|
|
52
|
+
type AimdRecordRenderValue,
|
|
53
|
+
type ReadonlyRecordAsset,
|
|
54
|
+
type ReadonlyRecordAssetKind,
|
|
55
|
+
type ReadonlyRecordAssetResolveContext,
|
|
56
|
+
type ReadonlyRecordAssetResolver,
|
|
57
|
+
type ReadonlyRecordMarkdownRenderOptions,
|
|
58
|
+
type ReadonlyRecordRenderContextInput,
|
|
59
|
+
type ReadonlyRecordVueRendererOptions,
|
|
60
|
+
} from './vue/readonly-record-renderer'
|
|
61
|
+
|
|
44
62
|
export {
|
|
45
63
|
type AimdComponentRenderer,
|
|
46
64
|
type AimdRendererContext,
|
package/src/styles/katex.css
CHANGED
|
@@ -109,3 +109,68 @@
|
|
|
109
109
|
visibility: visible;
|
|
110
110
|
transform: translateY(0);
|
|
111
111
|
}
|
|
112
|
+
|
|
113
|
+
.aimd-critic {
|
|
114
|
+
border-radius: 4px;
|
|
115
|
+
padding: 0 0.14em;
|
|
116
|
+
box-decoration-break: clone;
|
|
117
|
+
-webkit-box-decoration-break: clone;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.aimd-critic--addition {
|
|
121
|
+
background: rgba(22, 163, 74, 0.13);
|
|
122
|
+
color: #166534;
|
|
123
|
+
text-decoration: none;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.aimd-critic--deletion {
|
|
127
|
+
background: rgba(220, 38, 38, 0.1);
|
|
128
|
+
color: #991b1b;
|
|
129
|
+
text-decoration-line: line-through;
|
|
130
|
+
text-decoration-thickness: 0.08em;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.aimd-critic--highlight {
|
|
134
|
+
background: rgba(250, 204, 21, 0.36);
|
|
135
|
+
color: inherit;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.aimd-critic--substitution {
|
|
139
|
+
background: rgba(99, 102, 241, 0.08);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.aimd-critic--substitution .aimd-critic {
|
|
143
|
+
margin-inline: 0.04em;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.aimd-critic__replacement-arrow {
|
|
147
|
+
color: #64748b;
|
|
148
|
+
font-size: 0.86em;
|
|
149
|
+
font-weight: 600;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.aimd-critic--comment {
|
|
153
|
+
display: inline-flex;
|
|
154
|
+
align-items: baseline;
|
|
155
|
+
gap: 0.34em;
|
|
156
|
+
padding: 0.03em 0.44em;
|
|
157
|
+
border: 1px solid rgba(37, 99, 235, 0.22);
|
|
158
|
+
border-radius: 999px;
|
|
159
|
+
background: rgba(37, 99, 235, 0.09);
|
|
160
|
+
color: #1e3a8a;
|
|
161
|
+
font-size: 0.92em;
|
|
162
|
+
line-height: 1.5;
|
|
163
|
+
vertical-align: baseline;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.aimd-critic__comment-label {
|
|
167
|
+
color: #2563eb;
|
|
168
|
+
font-size: 0.78em;
|
|
169
|
+
font-weight: 700;
|
|
170
|
+
letter-spacing: 0.02em;
|
|
171
|
+
text-transform: uppercase;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.aimd-critic__comment-body {
|
|
175
|
+
color: inherit;
|
|
176
|
+
}
|
package/src/vue/index.ts
CHANGED
|
@@ -23,6 +23,24 @@ export {
|
|
|
23
23
|
type VueRendererOptions,
|
|
24
24
|
} from './vue-renderer'
|
|
25
25
|
|
|
26
|
+
export {
|
|
27
|
+
AIMD_RECORD_RENDER_SCOPES,
|
|
28
|
+
createReadonlyRecordAimdRenderers,
|
|
29
|
+
createReadonlyRecordElementRenderers,
|
|
30
|
+
createReadonlyRecordRenderContext,
|
|
31
|
+
normalizeRecordRenderValue,
|
|
32
|
+
renderReadonlyRecordToVue,
|
|
33
|
+
type AimdRecordRenderScope,
|
|
34
|
+
type AimdRecordRenderValue,
|
|
35
|
+
type ReadonlyRecordAsset,
|
|
36
|
+
type ReadonlyRecordAssetKind,
|
|
37
|
+
type ReadonlyRecordAssetResolveContext,
|
|
38
|
+
type ReadonlyRecordAssetResolver,
|
|
39
|
+
type ReadonlyRecordMarkdownRenderOptions,
|
|
40
|
+
type ReadonlyRecordRenderContextInput,
|
|
41
|
+
type ReadonlyRecordVueRendererOptions,
|
|
42
|
+
} from './readonly-record-renderer'
|
|
43
|
+
|
|
26
44
|
export {
|
|
27
45
|
renderToVue,
|
|
28
46
|
createRenderer,
|