@gmb/bitmark-parser-generator 5.23.0 → 5.25.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/browser/bitmark-parser-generator.min.js +1 -1
- package/dist/browser/bundle-report.html +2 -2
- package/dist/browser/cjs/index.cjs +2897 -167
- package/dist/browser/cjs/index.cjs.map +1 -1
- package/dist/browser/cjs/index.d.cts +195 -1
- package/dist/browser/esm/index.d.ts +195 -1
- package/dist/browser/esm/index.js +2895 -167
- package/dist/browser/esm/index.js.map +1 -1
- package/dist/cli/main.js +3039 -229
- package/dist/cli/main.js.map +1 -1
- package/dist/index.cjs +2924 -171
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +195 -1
- package/dist/index.d.ts +195 -1
- package/dist/index.js +2922 -171
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/cli/main.js
CHANGED
|
@@ -2,7 +2,7 @@ import process2 from 'process';
|
|
|
2
2
|
import { Command, Option, Help, Argument, InvalidArgumentError } from 'commander';
|
|
3
3
|
import { Enum } from '@ncoderz/superenum';
|
|
4
4
|
import path3 from 'path';
|
|
5
|
-
import
|
|
5
|
+
import fs4 from 'fs-extra';
|
|
6
6
|
import 'fs';
|
|
7
7
|
import os from 'os';
|
|
8
8
|
|
|
@@ -230,6 +230,7 @@ var BitType = {
|
|
|
230
230
|
dangerAlt: "danger-alt",
|
|
231
231
|
dangerCollapsible: "danger-collapsible",
|
|
232
232
|
definitionList: "definition-list",
|
|
233
|
+
definitionListCollapsible: "definition-list-collapsible",
|
|
233
234
|
definitionTerm: "definition-term",
|
|
234
235
|
deleted: "deleted",
|
|
235
236
|
details: "details",
|
|
@@ -510,6 +511,8 @@ var BitType = {
|
|
|
510
511
|
smartStandardArticleNonNormativeCollapsible: "smart-standard-article-non-normative-collapsible",
|
|
511
512
|
smartStandardArticleNormative: "smart-standard-article-normative",
|
|
512
513
|
smartStandardArticleNormativeCollapsible: "smart-standard-article-normative-collapsible",
|
|
514
|
+
smartStandardDefinitionListNonNormative: "smart-standard-definition-list-non-normative",
|
|
515
|
+
smartStandardDefinitionListNormative: "smart-standard-definition-list-normative",
|
|
513
516
|
smartStandardExampleNonNormative: "smart-standard-example-non-normative",
|
|
514
517
|
smartStandardExampleNonNormativeCollapsible: "smart-standard-example-non-normative-collapsible",
|
|
515
518
|
smartStandardExampleNormative: "smart-standard-example-normative",
|
|
@@ -560,6 +563,8 @@ var BitType = {
|
|
|
560
563
|
smartStandardTableNormativeCollapsible: "smart-standard-table-normative-collapsible",
|
|
561
564
|
standardArticleNonNormative: "standard-article-non-normative",
|
|
562
565
|
standardArticleNormative: "standard-article-normative",
|
|
566
|
+
standardDefinitionListNonNormative: "standard-definition-list-non-normative",
|
|
567
|
+
standardDefinitionListNormative: "standard-definition-list-normative",
|
|
563
568
|
standardExampleNonNormative: "standard-example-non-normative",
|
|
564
569
|
standardExampleNormative: "standard-example-normative",
|
|
565
570
|
standardImageFigureNonNormative: "standard-image-figure-non-normative",
|
|
@@ -709,10 +714,13 @@ var TextFormat = {
|
|
|
709
714
|
};
|
|
710
715
|
var CardSetConfigKey = {
|
|
711
716
|
flashcard: "flashcard",
|
|
717
|
+
flashcard1: "flashcard1",
|
|
712
718
|
definitionList: "definitionList",
|
|
719
|
+
definitionListPlain: "definitionListPlain",
|
|
713
720
|
elements: "elements",
|
|
714
721
|
statements: "statements",
|
|
715
722
|
quiz: "quiz",
|
|
723
|
+
quizResponses: "quizResponses",
|
|
716
724
|
feedback: "feedback",
|
|
717
725
|
questions: "questions",
|
|
718
726
|
matchPairs: "matchPairs",
|
|
@@ -724,6 +732,7 @@ var CardSetConfigKey = {
|
|
|
724
732
|
pronunciationTable: "pronunciationTable",
|
|
725
733
|
botActionResponses: "botActionResponses",
|
|
726
734
|
exampleBitList: "exampleBitList",
|
|
735
|
+
pageFooterSections: "pageFooterSections",
|
|
727
736
|
clozeList: "clozeList",
|
|
728
737
|
ingredients: "ingredients",
|
|
729
738
|
// DEPRECATED - TO BE REMOVED IN THE FUTURE
|
|
@@ -1128,12 +1137,17 @@ var groupKeys = {
|
|
|
1128
1137
|
group_standardAllBits: "group_standardAllBits",
|
|
1129
1138
|
group_standardItemLead: "group_standardItemLead",
|
|
1130
1139
|
group_standardItemLeadInstructionHint: "group_standardItemLeadInstructionHint",
|
|
1140
|
+
group_standardInstructionHint: "group_standardInstructionHint",
|
|
1131
1141
|
group_standardTags: "group_standardTags",
|
|
1142
|
+
group_standardTagsNoItemLead: "group_standardTagsNoItemLead",
|
|
1132
1143
|
group_imageSource: "group_imageSource",
|
|
1133
1144
|
group_technicalTerm: "group_technicalTerm",
|
|
1134
1145
|
group_person: "group_person",
|
|
1135
1146
|
group_gap: "group_gap",
|
|
1136
1147
|
group_trueFalse: "group_trueFalse",
|
|
1148
|
+
group_trueFalseInlineHighlight: "group_trueFalseInlineHighlight",
|
|
1149
|
+
group_trueFalseInlineSelect: "group_trueFalseInlineSelect",
|
|
1150
|
+
group_trueFalseResponses: "group_trueFalseResponses",
|
|
1137
1151
|
group_markConfig: "group_markConfig",
|
|
1138
1152
|
group_mark: "group_mark",
|
|
1139
1153
|
group_bookCommon: "group_bookCommon",
|
|
@@ -1141,14 +1155,18 @@ var groupKeys = {
|
|
|
1141
1155
|
group_advertisingCommon: "group_advertisingCommon",
|
|
1142
1156
|
group_platformStylesCommon: "group_platformStylesCommon",
|
|
1143
1157
|
group_quizCommon: "group_quizCommon",
|
|
1158
|
+
group_backgroundWallpaper: "group_backgroundWallpaper",
|
|
1159
|
+
group_imageNoZoom: "group_imageNoZoom",
|
|
1144
1160
|
group_resourceBitTags: "group_resourceBitTags",
|
|
1145
1161
|
group_resourceCommon: "group_resourceCommon",
|
|
1146
1162
|
group_resourceImageCommon: "group_resourceImageCommon",
|
|
1163
|
+
group_resourceImageCommonNoZoom: "group_resourceImageCommonNoZoom",
|
|
1147
1164
|
group_resourceAudioCommon: "group_resourceAudioCommon",
|
|
1148
1165
|
group_resourceVideoCommon: "group_resourceVideoCommon",
|
|
1149
1166
|
group_resourceIcon: "group_resourceIcon",
|
|
1150
1167
|
// Alias for image
|
|
1151
1168
|
group_resourceImage: "group_resourceImage",
|
|
1169
|
+
group_resourceImageNoZoom: "group_resourceImageNoZoom",
|
|
1152
1170
|
group_resourceImageEmbed: "group_resourceImageEmbed",
|
|
1153
1171
|
group_resourceImageLink: "group_resourceImageLink",
|
|
1154
1172
|
group_resourceImageResponsive: "group_resourceImageResponsive",
|
|
@@ -1297,6 +1315,7 @@ var propertyKeys = {
|
|
|
1297
1315
|
property_enforceUpdateOverUserInput: "@enforceUpdateOverUserInput",
|
|
1298
1316
|
property_extractorExtractionTimestamp: "@extractorExtractionTimestamp",
|
|
1299
1317
|
property_isCaseSensitive: "@isCaseSensitive",
|
|
1318
|
+
property_isCollapsible: "@isCollapsible",
|
|
1300
1319
|
property_isInfoOnly: "@isInfoOnly",
|
|
1301
1320
|
property_isPublic: "@isPublic",
|
|
1302
1321
|
property_isTemplate: "@isTemplate",
|
|
@@ -1422,6 +1441,7 @@ var propertyKeys = {
|
|
|
1422
1441
|
property_src4x: "@src4x",
|
|
1423
1442
|
property_srcAlt: "@srcAlt",
|
|
1424
1443
|
property_stripePricingTableId: "@stripePricingTableId",
|
|
1444
|
+
property_stripePricingTableDescriptiveName: "@stripePricingTableDescriptiveName",
|
|
1425
1445
|
property_stripePublishableKey: "@stripePublishableKey",
|
|
1426
1446
|
property_subject: "@subject",
|
|
1427
1447
|
property_subtype: "@subtype",
|
|
@@ -2164,7 +2184,7 @@ var CARDSETS = {
|
|
|
2164
2184
|
variants: [
|
|
2165
2185
|
{
|
|
2166
2186
|
jsonKey: "question.text",
|
|
2167
|
-
exportJsonKey: { question: { text: "$" } },
|
|
2187
|
+
exportJsonKey: [{ "@absent": { question: {} } }, { question: { text: "$" } }],
|
|
2168
2188
|
tags: [
|
|
2169
2189
|
{
|
|
2170
2190
|
key: ConfigKey.group_standardTags,
|
|
@@ -2180,6 +2200,136 @@ var CARDSETS = {
|
|
|
2180
2200
|
description: "Icon resource for the flashcard.",
|
|
2181
2201
|
jsonKey: "question.icon|resource(type=image, key=image)",
|
|
2182
2202
|
exportJsonKey: { question: { icon: { type: "image", image: { src: "$" } } } }
|
|
2203
|
+
},
|
|
2204
|
+
{
|
|
2205
|
+
key: ConfigKey.property_example,
|
|
2206
|
+
exportJsonKey: [
|
|
2207
|
+
{ "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2208
|
+
{ "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2209
|
+
{ isExample: true, example: "$", "@bit": { isExample: true } }
|
|
2210
|
+
],
|
|
2211
|
+
description: "Example marker for the flashcard question.",
|
|
2212
|
+
format: TagFormat.bitmarkText,
|
|
2213
|
+
nullable: true
|
|
2214
|
+
}
|
|
2215
|
+
]
|
|
2216
|
+
}
|
|
2217
|
+
]
|
|
2218
|
+
},
|
|
2219
|
+
{
|
|
2220
|
+
name: "answer",
|
|
2221
|
+
variants: [
|
|
2222
|
+
{
|
|
2223
|
+
jsonKey: "answer.text",
|
|
2224
|
+
exportJsonKey: [{ "@absent": { answer: {} } }, { answer: { text: "$" } }],
|
|
2225
|
+
tags: [
|
|
2226
|
+
{
|
|
2227
|
+
key: ConfigKey.group_standardTags,
|
|
2228
|
+
description: "Standard tags for the flashcard."
|
|
2229
|
+
},
|
|
2230
|
+
{
|
|
2231
|
+
exportJsonKey: { title: "$" },
|
|
2232
|
+
key: ConfigKey.tag_title,
|
|
2233
|
+
description: "Title of the flashcard."
|
|
2234
|
+
},
|
|
2235
|
+
{
|
|
2236
|
+
key: ConfigKey.group_resourceIcon,
|
|
2237
|
+
description: "Icon resource for the flashcard.",
|
|
2238
|
+
jsonKey: "answer.icon|resource(type=image, key=image)",
|
|
2239
|
+
exportJsonKey: { answer: { icon: { type: "image", image: { src: "$" } } } }
|
|
2240
|
+
},
|
|
2241
|
+
{
|
|
2242
|
+
key: ConfigKey.property_example,
|
|
2243
|
+
exportJsonKey: [
|
|
2244
|
+
{ "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2245
|
+
{ "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2246
|
+
{ isExample: true, example: "$", "@bit": { isExample: true } }
|
|
2247
|
+
],
|
|
2248
|
+
description: "Example marker for the flashcard answer.",
|
|
2249
|
+
format: TagFormat.bitmarkText,
|
|
2250
|
+
nullable: true
|
|
2251
|
+
}
|
|
2252
|
+
]
|
|
2253
|
+
},
|
|
2254
|
+
{
|
|
2255
|
+
jsonKey: "alternativeAnswers[].text",
|
|
2256
|
+
exportJsonKey: { alternativeAnswers: [{ text: "$" }] },
|
|
2257
|
+
tags: [
|
|
2258
|
+
{
|
|
2259
|
+
key: ConfigKey.group_standardTags,
|
|
2260
|
+
description: "Standard tags for the flashcard."
|
|
2261
|
+
},
|
|
2262
|
+
{
|
|
2263
|
+
exportJsonKey: { title: "$" },
|
|
2264
|
+
key: ConfigKey.tag_title,
|
|
2265
|
+
description: "Title of the flashcard."
|
|
2266
|
+
},
|
|
2267
|
+
{
|
|
2268
|
+
key: ConfigKey.group_resourceIcon,
|
|
2269
|
+
description: "Icon resource for the flashcard.",
|
|
2270
|
+
jsonKey: "alternativeAnswers[].icon|resource(type=image, key=image)",
|
|
2271
|
+
exportJsonKey: {
|
|
2272
|
+
alternativeAnswers: [{ icon: { type: "image", image: { src: "$" } } }]
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
],
|
|
2276
|
+
repeatCount: Count.infinity
|
|
2277
|
+
}
|
|
2278
|
+
]
|
|
2279
|
+
}
|
|
2280
|
+
]
|
|
2281
|
+
},
|
|
2282
|
+
//
|
|
2283
|
+
// flashcard1 — single-card variant of flashcard. Same shape, but capped
|
|
2284
|
+
// at one card via `sections.default.maxCount = 1` (PLAN-085). Used by
|
|
2285
|
+
// the `flashcard-1` bit type; the multi-card `flashcard` and
|
|
2286
|
+
// `q-and-a-card` bit types continue to use the unbounded `flashcard`
|
|
2287
|
+
// cardset.
|
|
2288
|
+
//
|
|
2289
|
+
[CardSetConfigKey.flashcard1]: {
|
|
2290
|
+
jsonKey: "cards",
|
|
2291
|
+
exportJsonKey: { cards: "$" },
|
|
2292
|
+
sections: {
|
|
2293
|
+
default: {
|
|
2294
|
+
jsonKey: "cards",
|
|
2295
|
+
exportJsonKey: { cards: "$" },
|
|
2296
|
+
isDefault: true,
|
|
2297
|
+
maxCount: 1
|
|
2298
|
+
}
|
|
2299
|
+
},
|
|
2300
|
+
sides: [
|
|
2301
|
+
{
|
|
2302
|
+
name: "question",
|
|
2303
|
+
variants: [
|
|
2304
|
+
{
|
|
2305
|
+
jsonKey: "question.text",
|
|
2306
|
+
exportJsonKey: [{ "@absent": { question: {} } }, { question: { text: "$" } }],
|
|
2307
|
+
tags: [
|
|
2308
|
+
{
|
|
2309
|
+
key: ConfigKey.group_standardTags,
|
|
2310
|
+
description: "Standard tags for the flashcard."
|
|
2311
|
+
},
|
|
2312
|
+
{
|
|
2313
|
+
exportJsonKey: { title: "$" },
|
|
2314
|
+
key: ConfigKey.tag_title,
|
|
2315
|
+
description: "Title of the flashcard."
|
|
2316
|
+
},
|
|
2317
|
+
{
|
|
2318
|
+
key: ConfigKey.group_resourceIcon,
|
|
2319
|
+
description: "Icon resource for the flashcard.",
|
|
2320
|
+
jsonKey: "question.icon|resource(type=image, key=image)",
|
|
2321
|
+
exportJsonKey: { question: { icon: { type: "image", image: { src: "$" } } } }
|
|
2322
|
+
},
|
|
2323
|
+
{
|
|
2324
|
+
key: ConfigKey.property_example,
|
|
2325
|
+
exportJsonKey: [
|
|
2326
|
+
{ "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2327
|
+
{ "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2328
|
+
{ isExample: true, example: "$", "@bit": { isExample: true } }
|
|
2329
|
+
],
|
|
2330
|
+
description: "Example marker for the flashcard question.",
|
|
2331
|
+
format: TagFormat.bitmarkText,
|
|
2332
|
+
nullable: true
|
|
2183
2333
|
}
|
|
2184
2334
|
]
|
|
2185
2335
|
}
|
|
@@ -2190,7 +2340,7 @@ var CARDSETS = {
|
|
|
2190
2340
|
variants: [
|
|
2191
2341
|
{
|
|
2192
2342
|
jsonKey: "answer.text",
|
|
2193
|
-
exportJsonKey: { answer: { text: "$" } },
|
|
2343
|
+
exportJsonKey: [{ "@absent": { answer: {} } }, { answer: { text: "$" } }],
|
|
2194
2344
|
tags: [
|
|
2195
2345
|
{
|
|
2196
2346
|
key: ConfigKey.group_standardTags,
|
|
@@ -2206,6 +2356,17 @@ var CARDSETS = {
|
|
|
2206
2356
|
description: "Icon resource for the flashcard.",
|
|
2207
2357
|
jsonKey: "answer.icon|resource(type=image, key=image)",
|
|
2208
2358
|
exportJsonKey: { answer: { icon: { type: "image", image: { src: "$" } } } }
|
|
2359
|
+
},
|
|
2360
|
+
{
|
|
2361
|
+
key: ConfigKey.property_example,
|
|
2362
|
+
exportJsonKey: [
|
|
2363
|
+
{ "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2364
|
+
{ "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2365
|
+
{ isExample: true, example: "$", "@bit": { isExample: true } }
|
|
2366
|
+
],
|
|
2367
|
+
description: "Example marker for the flashcard answer.",
|
|
2368
|
+
format: TagFormat.bitmarkText,
|
|
2369
|
+
nullable: true
|
|
2209
2370
|
}
|
|
2210
2371
|
]
|
|
2211
2372
|
},
|
|
@@ -2249,7 +2410,7 @@ var CARDSETS = {
|
|
|
2249
2410
|
variants: [
|
|
2250
2411
|
{
|
|
2251
2412
|
jsonKey: "term.text",
|
|
2252
|
-
exportJsonKey: { term: { text: "$" } },
|
|
2413
|
+
exportJsonKey: [{ "@absent": { term: {} } }, { term: { text: "$" } }],
|
|
2253
2414
|
tags: [
|
|
2254
2415
|
{
|
|
2255
2416
|
key: ConfigKey.group_standardTags,
|
|
@@ -2268,6 +2429,17 @@ var CARDSETS = {
|
|
|
2268
2429
|
format: TagFormat.plainText,
|
|
2269
2430
|
jsonKey: "term.icon|resource(type=image, key=image)",
|
|
2270
2431
|
exportJsonKey: { term: { icon: { type: "image", image: { src: "$" } } } }
|
|
2432
|
+
},
|
|
2433
|
+
{
|
|
2434
|
+
key: ConfigKey.property_example,
|
|
2435
|
+
exportJsonKey: [
|
|
2436
|
+
{ "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2437
|
+
{ "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2438
|
+
{ isExample: true, example: "$", "@bit": { isExample: true } }
|
|
2439
|
+
],
|
|
2440
|
+
description: "Example marker for the definition term.",
|
|
2441
|
+
format: TagFormat.bitmarkText,
|
|
2442
|
+
nullable: true
|
|
2271
2443
|
}
|
|
2272
2444
|
]
|
|
2273
2445
|
}
|
|
@@ -2278,7 +2450,7 @@ var CARDSETS = {
|
|
|
2278
2450
|
variants: [
|
|
2279
2451
|
{
|
|
2280
2452
|
jsonKey: "definition.text",
|
|
2281
|
-
exportJsonKey: { definition: { text: "$" } },
|
|
2453
|
+
exportJsonKey: [{ "@absent": { definition: {} } }, { definition: { text: "$" } }],
|
|
2282
2454
|
tags: [
|
|
2283
2455
|
{
|
|
2284
2456
|
key: ConfigKey.group_standardTags,
|
|
@@ -2296,6 +2468,17 @@ var CARDSETS = {
|
|
|
2296
2468
|
description: "Icon resource for the definition.",
|
|
2297
2469
|
jsonKey: "definition.icon|resource(type=image, key=image)",
|
|
2298
2470
|
exportJsonKey: { definition: { icon: { type: "image", image: { src: "$" } } } }
|
|
2471
|
+
},
|
|
2472
|
+
{
|
|
2473
|
+
key: ConfigKey.property_example,
|
|
2474
|
+
exportJsonKey: [
|
|
2475
|
+
{ "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2476
|
+
{ "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2477
|
+
{ isExample: true, example: "$", "@bit": { isExample: true } }
|
|
2478
|
+
],
|
|
2479
|
+
description: "Example marker for the definition.",
|
|
2480
|
+
format: TagFormat.bitmarkText,
|
|
2481
|
+
nullable: true
|
|
2299
2482
|
}
|
|
2300
2483
|
]
|
|
2301
2484
|
},
|
|
@@ -2328,6 +2511,126 @@ var CARDSETS = {
|
|
|
2328
2511
|
]
|
|
2329
2512
|
},
|
|
2330
2513
|
//
|
|
2514
|
+
// definition-list-plain
|
|
2515
|
+
//
|
|
2516
|
+
// Same shape as `definitionList`, but every variant body is rendered as
|
|
2517
|
+
// plain text (string), not bitmark+ AST. Used by
|
|
2518
|
+
// `meta-search-default-terms` / `meta-search-default-topics`, which need
|
|
2519
|
+
// term/definition text emitted verbatim instead of as a ProseMirror tree.
|
|
2520
|
+
//
|
|
2521
|
+
[CardSetConfigKey.definitionListPlain]: {
|
|
2522
|
+
jsonKey: "definitions",
|
|
2523
|
+
exportJsonKey: { definitions: "$" },
|
|
2524
|
+
sides: [
|
|
2525
|
+
{
|
|
2526
|
+
name: "term",
|
|
2527
|
+
variants: [
|
|
2528
|
+
{
|
|
2529
|
+
jsonKey: "term.text",
|
|
2530
|
+
exportJsonKey: [{ "@absent": { term: {} } }, { term: { text: "$" } }],
|
|
2531
|
+
format: TextFormat.plainText,
|
|
2532
|
+
tags: [
|
|
2533
|
+
{
|
|
2534
|
+
key: ConfigKey.group_standardTags,
|
|
2535
|
+
description: "Standard tags for the definition."
|
|
2536
|
+
},
|
|
2537
|
+
{
|
|
2538
|
+
key: ConfigKey.tag_title,
|
|
2539
|
+
description: "Title of the definition.",
|
|
2540
|
+
format: TagFormat.plainText,
|
|
2541
|
+
jsonKey: "^heading.forKeys",
|
|
2542
|
+
exportJsonKey: { "@bit": { heading: { forKeys: "$" } } }
|
|
2543
|
+
},
|
|
2544
|
+
{
|
|
2545
|
+
key: ConfigKey.group_resourceIcon,
|
|
2546
|
+
description: "Icon resource for the definition.",
|
|
2547
|
+
format: TagFormat.plainText,
|
|
2548
|
+
jsonKey: "term.icon|resource(type=image, key=image)",
|
|
2549
|
+
exportJsonKey: { term: { icon: { type: "image", image: { src: "$" } } } }
|
|
2550
|
+
},
|
|
2551
|
+
{
|
|
2552
|
+
key: ConfigKey.property_example,
|
|
2553
|
+
exportJsonKey: [
|
|
2554
|
+
{ "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2555
|
+
{ "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2556
|
+
{ isExample: true, example: "$", "@bit": { isExample: true } }
|
|
2557
|
+
],
|
|
2558
|
+
description: "Example marker for the definition term.",
|
|
2559
|
+
format: TagFormat.bitmarkText,
|
|
2560
|
+
nullable: true
|
|
2561
|
+
}
|
|
2562
|
+
]
|
|
2563
|
+
}
|
|
2564
|
+
]
|
|
2565
|
+
},
|
|
2566
|
+
{
|
|
2567
|
+
name: "definition",
|
|
2568
|
+
variants: [
|
|
2569
|
+
{
|
|
2570
|
+
jsonKey: "definition.text",
|
|
2571
|
+
exportJsonKey: [{ "@absent": { definition: {} } }, { definition: { text: "$" } }],
|
|
2572
|
+
format: TextFormat.plainText,
|
|
2573
|
+
tags: [
|
|
2574
|
+
{
|
|
2575
|
+
key: ConfigKey.group_standardTags,
|
|
2576
|
+
description: "Standard tags for the definition."
|
|
2577
|
+
},
|
|
2578
|
+
{
|
|
2579
|
+
key: ConfigKey.tag_title,
|
|
2580
|
+
description: "Title of the definition.",
|
|
2581
|
+
format: TagFormat.plainText,
|
|
2582
|
+
jsonKey: "^heading.forValues",
|
|
2583
|
+
exportJsonKey: { "@bit": { heading: { forValues: "$" } } }
|
|
2584
|
+
},
|
|
2585
|
+
{
|
|
2586
|
+
key: ConfigKey.group_resourceIcon,
|
|
2587
|
+
description: "Icon resource for the definition.",
|
|
2588
|
+
jsonKey: "definition.icon|resource(type=image, key=image)",
|
|
2589
|
+
exportJsonKey: { definition: { icon: { type: "image", image: { src: "$" } } } }
|
|
2590
|
+
},
|
|
2591
|
+
{
|
|
2592
|
+
key: ConfigKey.property_example,
|
|
2593
|
+
exportJsonKey: [
|
|
2594
|
+
{ "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2595
|
+
{ "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2596
|
+
{ isExample: true, example: "$", "@bit": { isExample: true } }
|
|
2597
|
+
],
|
|
2598
|
+
description: "Example marker for the definition.",
|
|
2599
|
+
format: TagFormat.bitmarkText,
|
|
2600
|
+
nullable: true
|
|
2601
|
+
}
|
|
2602
|
+
]
|
|
2603
|
+
},
|
|
2604
|
+
{
|
|
2605
|
+
jsonKey: "alternativeDefinitions[].text",
|
|
2606
|
+
exportJsonKey: { alternativeDefinitions: [{ text: "$" }] },
|
|
2607
|
+
format: TextFormat.plainText,
|
|
2608
|
+
tags: [
|
|
2609
|
+
{
|
|
2610
|
+
key: ConfigKey.group_standardTags,
|
|
2611
|
+
description: "Standard tags for the definition."
|
|
2612
|
+
},
|
|
2613
|
+
{
|
|
2614
|
+
exportJsonKey: { title: "$" },
|
|
2615
|
+
key: ConfigKey.tag_title,
|
|
2616
|
+
description: "Title of the definition."
|
|
2617
|
+
},
|
|
2618
|
+
{
|
|
2619
|
+
key: ConfigKey.group_resourceIcon,
|
|
2620
|
+
description: "Icon resource for the definition.",
|
|
2621
|
+
jsonKey: "alternativeDefinitions[].icon|resource(type=image, key=image)",
|
|
2622
|
+
exportJsonKey: {
|
|
2623
|
+
alternativeDefinitions: [{ icon: { type: "image", image: { src: "$" } } }]
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
],
|
|
2627
|
+
repeatCount: Count.infinity
|
|
2628
|
+
}
|
|
2629
|
+
]
|
|
2630
|
+
}
|
|
2631
|
+
]
|
|
2632
|
+
},
|
|
2633
|
+
//
|
|
2331
2634
|
// match-pairs
|
|
2332
2635
|
//
|
|
2333
2636
|
[CardSetConfigKey.matchPairs]: {
|
|
@@ -2340,11 +2643,51 @@ var CARDSETS = {
|
|
|
2340
2643
|
{
|
|
2341
2644
|
jsonKey: "key",
|
|
2342
2645
|
exportJsonKey: { key: "$" },
|
|
2646
|
+
format: TextFormat.plainText,
|
|
2343
2647
|
tags: [
|
|
2344
2648
|
{
|
|
2345
2649
|
key: ConfigKey.group_standardTags,
|
|
2346
2650
|
description: "Standard tags for the match pair."
|
|
2347
2651
|
},
|
|
2652
|
+
{
|
|
2653
|
+
// Pair-level example: emit `isExample` alongside `example`
|
|
2654
|
+
// (matches OLD parser's per-pair allow-list behaviour).
|
|
2655
|
+
//
|
|
2656
|
+
// PLAN-074: a valued cascade source (`[@example:V]` anywhere
|
|
2657
|
+
// in the cardset) fills each pair's `example` with `V`
|
|
2658
|
+
// once per pair from the keys-side cascade fire (single
|
|
2659
|
+
// parsed_variant per pair — no per-value duplication).
|
|
2660
|
+
//
|
|
2661
|
+
// PLAN-076 NOTE: matchPairs (TAG 415) KEEPS `example: '$'`
|
|
2662
|
+
// at the pair-object level. BPG `parseMatchPairs`
|
|
2663
|
+
// (`CardContentProcessor.ts:714-723`) emits ONE example
|
|
2664
|
+
// per PAIR (the `exampleCard` accumulator), placed at the
|
|
2665
|
+
// pair object. Unlike matchMatrix (per-cell example),
|
|
2666
|
+
// matchPairs has no nested per-cell layer, so the pair-key
|
|
2667
|
+
// `[@example:V]` must double-emit as the visible field.
|
|
2668
|
+
// Confirmed via `chaining` fixture: `match-solution-grouped`
|
|
2669
|
+
// expects `example: "packen"` at pair-object level from
|
|
2670
|
+
// `die Koffer[%1][@example:packen]`. Original PLAN-076
|
|
2671
|
+
// draft applied to both keys-side tags; the matchPairs
|
|
2672
|
+
// half was reverted after fixture parity testing.
|
|
2673
|
+
key: ConfigKey.property_example,
|
|
2674
|
+
exportJsonKey: [
|
|
2675
|
+
{ "@keyonly": { isExample: true, "@bit": { isExample: true } } },
|
|
2676
|
+
{
|
|
2677
|
+
predicates: ["@absent", { $cascade: "*" }],
|
|
2678
|
+
rule: {
|
|
2679
|
+
isExample: true,
|
|
2680
|
+
example: "$cascade",
|
|
2681
|
+
"@bit": { isExample: true }
|
|
2682
|
+
}
|
|
2683
|
+
},
|
|
2684
|
+
{ "@absent": { isExample: true } },
|
|
2685
|
+
{ isExample: true, example: "$", "@bit": { isExample: true } }
|
|
2686
|
+
],
|
|
2687
|
+
description: "Example marker / value on the match pair.",
|
|
2688
|
+
format: TagFormat.bitmarkText,
|
|
2689
|
+
nullable: true
|
|
2690
|
+
},
|
|
2348
2691
|
{
|
|
2349
2692
|
key: ConfigKey.tag_title,
|
|
2350
2693
|
description: "Title of the match pair.",
|
|
@@ -2353,9 +2696,19 @@ var CARDSETS = {
|
|
|
2353
2696
|
exportJsonKey: { "@bit": { heading: { forKeys: "$" } } }
|
|
2354
2697
|
},
|
|
2355
2698
|
{
|
|
2699
|
+
// PLAN-084: gate the key-side `@absent → $ancestor` cascade
|
|
2700
|
+
// synth with `@variantLacksTag=isCaseSensitive` so the rule
|
|
2701
|
+
// only fires when NO variant in the active card explicitly
|
|
2702
|
+
// carries `[@isCaseSensitive]`. When a value-side variant
|
|
2703
|
+
// carries the tag (e.g. `[@isCaseSensitive:false]`), the
|
|
2704
|
+
// synth is skipped here; the value-side explicit fire owns
|
|
2705
|
+
// the pair-level emission.
|
|
2356
2706
|
key: ConfigKey.property_isCaseSensitive,
|
|
2357
2707
|
exportJsonKey: [
|
|
2358
|
-
{
|
|
2708
|
+
{
|
|
2709
|
+
predicates: ["@absent", { "@variantLacksTag": "@isCaseSensitive" }],
|
|
2710
|
+
rule: { isCaseSensitive: "$ancestor" }
|
|
2711
|
+
},
|
|
2359
2712
|
{ isCaseSensitive: "$" }
|
|
2360
2713
|
],
|
|
2361
2714
|
description: "Property to indicate if the match is case sensitive.",
|
|
@@ -2384,10 +2737,85 @@ var CARDSETS = {
|
|
|
2384
2737
|
{
|
|
2385
2738
|
jsonKey: "values[]",
|
|
2386
2739
|
exportJsonKey: { values: ["$"] },
|
|
2740
|
+
format: TextFormat.plainText,
|
|
2387
2741
|
tags: [
|
|
2388
2742
|
{
|
|
2389
|
-
|
|
2390
|
-
|
|
2743
|
+
// PLAN-084: value-side excludes item/lead/pageNumber/marginNumber.
|
|
2744
|
+
// BPG `parseMatchPairs` does `delete tags.item; delete tags.lead;`
|
|
2745
|
+
// for sideIdx > 0; the schema-side equivalent is to use a
|
|
2746
|
+
// group_standardTags variant that omits group_standardItemLead.
|
|
2747
|
+
key: ConfigKey.group_standardTagsNoItemLead,
|
|
2748
|
+
description: "Standard tags for the match pair (values side, no item/lead)."
|
|
2749
|
+
},
|
|
2750
|
+
{
|
|
2751
|
+
// PLAN-072 + ALIGN-EXAMPLE-CASCADE §3.6: cascade-fired
|
|
2752
|
+
// `@example` from the bit header fills each pair's
|
|
2753
|
+
// `example` with the pair's first value (mirrors BPG's
|
|
2754
|
+
// `fillStringExample(pairs, __defaultExample=values[0])`).
|
|
2755
|
+
//
|
|
2756
|
+
// PLAN-074: the cascade-source-VALUE branch is handled by
|
|
2757
|
+
// TAG 415 (keys side) which fires once per pair — keeping
|
|
2758
|
+
// it here would emit `example` twice per pair (one per
|
|
2759
|
+
// values-side parsed_variant) and merge them into a
|
|
2760
|
+
// multi-paragraph array.
|
|
2761
|
+
//
|
|
2762
|
+
// PLAN-075: bare `$parent` reads the parent variant fire's
|
|
2763
|
+
// source `$` value (the current `++`-iteration's value
|
|
2764
|
+
// text). For single-value pairs this matches the old
|
|
2765
|
+
// `$parent.values[0]`; for multi-value pairs with
|
|
2766
|
+
// `[@example]` attached to a non-first value, it correctly
|
|
2767
|
+
// resolves to that value.
|
|
2768
|
+
//
|
|
2769
|
+
// PLAN-077: dual `[@absent, { '$cascade': '*' }]` rule
|
|
2770
|
+
// routes pair-key `[@example:V]` (a per-pair cascade
|
|
2771
|
+
// source, bound via per-card CascadeCounters scope) and
|
|
2772
|
+
// bit-header `[@example:V]` (cardset cascade) into the
|
|
2773
|
+
// pair-level `example` via `$cascade`. The aggregator in
|
|
2774
|
+
// `populate_variants` fires this rule ONCE per pair (BPG
|
|
2775
|
+
// `parseMatchPairs` `exampleCard` precedence), avoiding
|
|
2776
|
+
// per-iter pollution.
|
|
2777
|
+
key: ConfigKey.property_example,
|
|
2778
|
+
exportJsonKey: [
|
|
2779
|
+
// PLAN-084: bare `[@example]` on a values-side variant fires
|
|
2780
|
+
// once per pair (first-wins, mirrors BPG `parseMatchPairs`
|
|
2781
|
+
// `exampleCard` + `pushDownTree` set-if-null semantic).
|
|
2782
|
+
// `maxEmits: 1` enforced inside the per-pair cascade-counter
|
|
2783
|
+
// scope opened by `build_card_item`.
|
|
2784
|
+
{
|
|
2785
|
+
predicates: ["@keyonly"],
|
|
2786
|
+
maxEmits: 1,
|
|
2787
|
+
rule: {
|
|
2788
|
+
isExample: true,
|
|
2789
|
+
example: "$parent",
|
|
2790
|
+
"@bit": { isExample: true }
|
|
2791
|
+
}
|
|
2792
|
+
},
|
|
2793
|
+
{
|
|
2794
|
+
predicates: ["@absent", { $cascade: "*" }],
|
|
2795
|
+
rule: {
|
|
2796
|
+
isExample: true,
|
|
2797
|
+
example: "$cascade",
|
|
2798
|
+
"@bit": { isExample: true }
|
|
2799
|
+
}
|
|
2800
|
+
},
|
|
2801
|
+
{
|
|
2802
|
+
"@absent": {
|
|
2803
|
+
isExample: true,
|
|
2804
|
+
example: "$parent",
|
|
2805
|
+
"@bit": { isExample: true }
|
|
2806
|
+
}
|
|
2807
|
+
},
|
|
2808
|
+
// PLAN-084: same cap applies to the explicit-value `[@example:V]`
|
|
2809
|
+
// form so per-`++`-iter values-side fires also first-win.
|
|
2810
|
+
{
|
|
2811
|
+
predicates: [],
|
|
2812
|
+
maxEmits: 1,
|
|
2813
|
+
rule: { isExample: true, example: "$", "@bit": { isExample: true } }
|
|
2814
|
+
}
|
|
2815
|
+
],
|
|
2816
|
+
description: "Example marker / value on the match pair (values side).",
|
|
2817
|
+
format: TagFormat.bitmarkText,
|
|
2818
|
+
nullable: true
|
|
2391
2819
|
},
|
|
2392
2820
|
{
|
|
2393
2821
|
key: ConfigKey.tag_title,
|
|
@@ -2397,9 +2825,13 @@ var CARDSETS = {
|
|
|
2397
2825
|
exportJsonKey: { "@bit": { heading: { forValues: "$" } } }
|
|
2398
2826
|
},
|
|
2399
2827
|
{
|
|
2828
|
+
// PLAN-084: symmetric @variantLacksTag gating with the key-side.
|
|
2400
2829
|
key: ConfigKey.property_isCaseSensitive,
|
|
2401
2830
|
exportJsonKey: [
|
|
2402
|
-
{
|
|
2831
|
+
{
|
|
2832
|
+
predicates: ["@absent", { "@variantLacksTag": "@isCaseSensitive" }],
|
|
2833
|
+
rule: { isCaseSensitive: "$ancestor" }
|
|
2834
|
+
},
|
|
2403
2835
|
{ isCaseSensitive: "$" }
|
|
2404
2836
|
],
|
|
2405
2837
|
description: "Property to indicate if the match is case sensitive.",
|
|
@@ -2425,6 +2857,7 @@ var CARDSETS = {
|
|
|
2425
2857
|
{
|
|
2426
2858
|
jsonKey: "key",
|
|
2427
2859
|
exportJsonKey: { key: "$" },
|
|
2860
|
+
format: TextFormat.plainText,
|
|
2428
2861
|
tags: [
|
|
2429
2862
|
{
|
|
2430
2863
|
key: ConfigKey.group_standardTags,
|
|
@@ -2454,10 +2887,12 @@ var CARDSETS = {
|
|
|
2454
2887
|
{
|
|
2455
2888
|
jsonKey: "values[]",
|
|
2456
2889
|
exportJsonKey: { values: ["$"] },
|
|
2890
|
+
format: TextFormat.plainText,
|
|
2457
2891
|
tags: [
|
|
2458
2892
|
{
|
|
2459
|
-
|
|
2460
|
-
|
|
2893
|
+
// PLAN-084: value-side excludes item/lead/pageNumber/marginNumber.
|
|
2894
|
+
key: ConfigKey.group_standardTagsNoItemLead,
|
|
2895
|
+
description: "Standard tags for the audio match pair (values side, no item/lead)."
|
|
2461
2896
|
},
|
|
2462
2897
|
{
|
|
2463
2898
|
key: ConfigKey.tag_title,
|
|
@@ -2490,6 +2925,7 @@ var CARDSETS = {
|
|
|
2490
2925
|
{
|
|
2491
2926
|
jsonKey: "key",
|
|
2492
2927
|
exportJsonKey: { key: "$" },
|
|
2928
|
+
format: TextFormat.plainText,
|
|
2493
2929
|
tags: [
|
|
2494
2930
|
{
|
|
2495
2931
|
key: ConfigKey.group_standardTags,
|
|
@@ -2519,10 +2955,12 @@ var CARDSETS = {
|
|
|
2519
2955
|
{
|
|
2520
2956
|
jsonKey: "values[]",
|
|
2521
2957
|
exportJsonKey: { values: ["$"] },
|
|
2958
|
+
format: TextFormat.plainText,
|
|
2522
2959
|
tags: [
|
|
2523
2960
|
{
|
|
2524
|
-
|
|
2525
|
-
|
|
2961
|
+
// PLAN-084: value-side excludes item/lead/pageNumber/marginNumber.
|
|
2962
|
+
key: ConfigKey.group_standardTagsNoItemLead,
|
|
2963
|
+
description: "Standard tags for the image match pair (values side, no item/lead)."
|
|
2526
2964
|
},
|
|
2527
2965
|
{
|
|
2528
2966
|
key: ConfigKey.tag_title,
|
|
@@ -2555,20 +2993,49 @@ var CARDSETS = {
|
|
|
2555
2993
|
{
|
|
2556
2994
|
jsonKey: "key",
|
|
2557
2995
|
exportJsonKey: { key: "$" },
|
|
2996
|
+
format: TextFormat.plainText,
|
|
2558
2997
|
tags: [
|
|
2559
2998
|
{
|
|
2560
2999
|
key: ConfigKey.group_standardItemLeadInstructionHint,
|
|
2561
3000
|
description: "Standard tags for lead, instruction, and hint."
|
|
2562
3001
|
},
|
|
2563
3002
|
{
|
|
3003
|
+
// PLAN-076: drop `example: '$'` from valued rule — pair-key
|
|
3004
|
+
// `[@example:V]` is consumed as the per-pair cascade source.
|
|
3005
|
+
// PLAN-077 R14 follow-up: investigation showed the fixture
|
|
3006
|
+
// reference is INCONSISTENT — bits without a cardset-level
|
|
3007
|
+
// cascade (e.g. bit 3 `[@example:Y]should be...`) expect
|
|
3008
|
+
// pair-level `example: V` at pair scope, but bits WITH a
|
|
3009
|
+
// cardset cascade (e.g. bit 8 `[@example:99]` in title-row
|
|
3010
|
+
// PLUS pair-key `[@example:Y]`) expect NO pair-level
|
|
3011
|
+
// example. Emitting `example: $` here satisfies bit 3 but
|
|
3012
|
+
// breaks bit 8; dropping it satisfies bit 8 but breaks bit
|
|
3013
|
+
// 3. Conditional emission would require runtime gating on
|
|
3014
|
+
// the cardset cascade scope state. Left dropped pending a
|
|
3015
|
+
// dedicated plan — accepted divergence on bit 3's pair-
|
|
3016
|
+
// level emission (cells still get the per-cell `example`
|
|
3017
|
+
// correctly via PLAN-077 §4.2 + §4.3).
|
|
2564
3018
|
key: ConfigKey.property_example,
|
|
2565
3019
|
exportJsonKey: [
|
|
2566
|
-
{
|
|
2567
|
-
|
|
2568
|
-
|
|
3020
|
+
{
|
|
3021
|
+
"@keyonly": {
|
|
3022
|
+
isExample: true,
|
|
3023
|
+
"@bit": { isExample: true },
|
|
3024
|
+
"@card": { isExample: true }
|
|
3025
|
+
}
|
|
3026
|
+
},
|
|
3027
|
+
{
|
|
3028
|
+
"@absent": {
|
|
3029
|
+
isExample: true,
|
|
3030
|
+
"@bit": { isExample: true },
|
|
3031
|
+
"@card": { isExample: true }
|
|
3032
|
+
}
|
|
3033
|
+
},
|
|
3034
|
+
{ isExample: true, "@bit": { isExample: true }, "@card": { isExample: true } }
|
|
2569
3035
|
],
|
|
2570
3036
|
description: "Example text for the match matrix.",
|
|
2571
|
-
format: TagFormat.plainText
|
|
3037
|
+
format: TagFormat.plainText,
|
|
3038
|
+
nullable: true
|
|
2572
3039
|
},
|
|
2573
3040
|
{
|
|
2574
3041
|
key: ConfigKey.tag_title,
|
|
@@ -2596,35 +3063,96 @@ var CARDSETS = {
|
|
|
2596
3063
|
variants: [
|
|
2597
3064
|
{
|
|
2598
3065
|
jsonKey: "cells[{s}].values[]",
|
|
2599
|
-
exportJsonKey:
|
|
3066
|
+
exportJsonKey: [
|
|
3067
|
+
// PLAN-085 / R14: the positional-blank emission for an
|
|
3068
|
+
// effectively-absent cell side fires ONLY when the active
|
|
3069
|
+
// card has at least one `[#]` variant fire. This matches
|
|
3070
|
+
// BPG `extractHeadingCard`'s `isHeading` gate: when no
|
|
3071
|
+
// `[#]` exists anywhere in the title card, BPG returns
|
|
3072
|
+
// undefined and no `heading` is emitted. The phantom
|
|
3073
|
+
// `forValues:['']` placeholder is appropriate only when
|
|
3074
|
+
// the card is being treated as a title-row.
|
|
3075
|
+
{
|
|
3076
|
+
predicates: ["@absent", { "@variantHasTag": "#" }],
|
|
3077
|
+
rule: { "@bit": { heading: { forValues: [""] } } }
|
|
3078
|
+
},
|
|
3079
|
+
{ cells: { $s: { values: ["$"] } } }
|
|
3080
|
+
],
|
|
3081
|
+
format: TextFormat.plainText,
|
|
2600
3082
|
tags: [
|
|
2601
3083
|
{
|
|
2602
3084
|
key: ConfigKey.group_standardItemLeadInstructionHint,
|
|
2603
3085
|
description: "Standard tags for lead, instruction, and hint."
|
|
2604
3086
|
},
|
|
2605
3087
|
{
|
|
3088
|
+
// PLAN-075: bare `$parent` reads the parent variant fire's
|
|
3089
|
+
// source `$` value (the current `++`-iteration's cell text).
|
|
3090
|
+
// Replaces `$parent.values[0]` which pinned to position 0
|
|
3091
|
+
// regardless of which `++`-value `[@example]` was on.
|
|
3092
|
+
//
|
|
3093
|
+
// PLAN-077: dual `[@absent, { '$cascade': '*' }]` rule
|
|
3094
|
+
// routes pair-key `[@example:V]` (a per-pair cascade
|
|
3095
|
+
// source, bound via per-card CascadeCounters scope) and
|
|
3096
|
+
// bit-header `[@example:V]` (cardset cascade) into the
|
|
3097
|
+
// per-cell `example` via `$cascade`. The aggregator in
|
|
3098
|
+
// `populate_variants` fires this rule ONCE per cell (BPG
|
|
3099
|
+
// `parseMatchMatrix` `exampleSide` precedence), avoiding
|
|
3100
|
+
// per-iter pollution.
|
|
3101
|
+
// @card scope-shift writes matrix-row container isExample.
|
|
2606
3102
|
key: ConfigKey.property_example,
|
|
2607
3103
|
exportJsonKey: [
|
|
2608
3104
|
{
|
|
2609
3105
|
"@keyonly": {
|
|
2610
|
-
cells: { $s: { isExample: true, example: "$parent
|
|
2611
|
-
"@bit": { isExample: true }
|
|
3106
|
+
cells: { $s: { isExample: true, example: "$parent" } },
|
|
3107
|
+
"@bit": { isExample: true },
|
|
3108
|
+
"@card": { isExample: true }
|
|
2612
3109
|
}
|
|
2613
3110
|
},
|
|
2614
3111
|
{
|
|
2615
|
-
"@absent"
|
|
3112
|
+
predicates: ["@absent", { $cascade: "*" }],
|
|
3113
|
+
rule: {
|
|
3114
|
+
cells: { $s: { isExample: true, example: "$cascade" } },
|
|
3115
|
+
"@bit": { isExample: true },
|
|
3116
|
+
"@card": { isExample: true }
|
|
3117
|
+
}
|
|
2616
3118
|
},
|
|
2617
|
-
{
|
|
3119
|
+
{
|
|
3120
|
+
"@absent": {
|
|
3121
|
+
cells: { $s: { isExample: true, example: "$parent" } },
|
|
3122
|
+
"@bit": { isExample: true },
|
|
3123
|
+
"@card": { isExample: true }
|
|
3124
|
+
}
|
|
3125
|
+
},
|
|
3126
|
+
{
|
|
3127
|
+
cells: { $s: { isExample: true, example: "$" } },
|
|
3128
|
+
"@bit": { isExample: true },
|
|
3129
|
+
"@card": { isExample: true }
|
|
3130
|
+
}
|
|
2618
3131
|
],
|
|
2619
3132
|
description: "Example text for the match matrix.",
|
|
2620
|
-
format: TagFormat.
|
|
3133
|
+
format: TagFormat.bitmarkText,
|
|
3134
|
+
nullable: true
|
|
2621
3135
|
},
|
|
2622
3136
|
{
|
|
3137
|
+
// PLAN-085 / R14: bare `[#]` on a value-side emits the
|
|
3138
|
+
// empty-string positional blank into `forValues` so the
|
|
3139
|
+
// array stays aligned with the cell positions (matches BPG
|
|
3140
|
+
// `extractHeadingCard`'s "all value-side headings present"
|
|
3141
|
+
// shape). The `@keyonly` rule is required for two reasons:
|
|
3142
|
+
// 1) it provides an emission for bare `[#]` (the `$` sigil
|
|
3143
|
+
// in the unconditional rule has no source for bare); and
|
|
3144
|
+
// 2) the presence of any `@keyonly` rule disables the
|
|
3145
|
+
// optimised-mode `should_suppress_outcome` strip (see
|
|
3146
|
+
// `forward_emit.rs`) so the resulting `forValues: [""]`
|
|
3147
|
+
// survives the all-natural-defaults check.
|
|
2623
3148
|
key: ConfigKey.tag_title,
|
|
2624
3149
|
description: "Title of the match matrix.",
|
|
2625
3150
|
format: TagFormat.plainText,
|
|
2626
3151
|
jsonKey: "^heading.forValues",
|
|
2627
|
-
exportJsonKey:
|
|
3152
|
+
exportJsonKey: [
|
|
3153
|
+
{ "@keyonly": { "@bit": { heading: { forValues: [""] } } } },
|
|
3154
|
+
{ "@bit": { heading: { forValues: ["$"] } } }
|
|
3155
|
+
]
|
|
2628
3156
|
},
|
|
2629
3157
|
{
|
|
2630
3158
|
key: ConfigKey.property_isCaseSensitive,
|
|
@@ -2673,6 +3201,35 @@ var CARDSETS = {
|
|
|
2673
3201
|
{
|
|
2674
3202
|
key: ConfigKey.group_standardTags,
|
|
2675
3203
|
description: "Standard tags for the statement."
|
|
3204
|
+
},
|
|
3205
|
+
{
|
|
3206
|
+
// PLAN-072: cascade-fired `@example` from the bit header
|
|
3207
|
+
// fills each statement entry's `example` with the entry's
|
|
3208
|
+
// own `isCorrect`, mirroring BPG's `pushExampleDownTree`
|
|
3209
|
+
// for the statements cardset. Per-array-item strip removes
|
|
3210
|
+
// `example: false` from incorrect entries so only correct
|
|
3211
|
+
// entries retain `example: true`.
|
|
3212
|
+
key: ConfigKey.property_example,
|
|
3213
|
+
exportJsonKey: [
|
|
3214
|
+
{
|
|
3215
|
+
"@keyonly": {
|
|
3216
|
+
isExample: true,
|
|
3217
|
+
example: true,
|
|
3218
|
+
"@bit": { isExample: true }
|
|
3219
|
+
}
|
|
3220
|
+
},
|
|
3221
|
+
{
|
|
3222
|
+
"@absent": {
|
|
3223
|
+
isExample: true,
|
|
3224
|
+
example: "$parent.isCorrect",
|
|
3225
|
+
"@bit": { isExample: true }
|
|
3226
|
+
}
|
|
3227
|
+
},
|
|
3228
|
+
{ isExample: true, example: "$", "@bit": { isExample: true } }
|
|
3229
|
+
],
|
|
3230
|
+
description: "Example marker on the statement. Bare `[@example]` emits the boolean marker; valued `[@example:true|false]` emits the literal value. Cascade from bit-header fills `example` with the entry\u2019s own `isCorrect`.",
|
|
3231
|
+
format: TagFormat.boolean,
|
|
3232
|
+
nullable: true
|
|
2676
3233
|
}
|
|
2677
3234
|
],
|
|
2678
3235
|
bodyAllowed: false
|
|
@@ -2701,6 +3258,71 @@ var CARDSETS = {
|
|
|
2701
3258
|
{
|
|
2702
3259
|
key: ConfigKey.group_standardTags,
|
|
2703
3260
|
description: "Standard tags for the quiz."
|
|
3261
|
+
},
|
|
3262
|
+
{
|
|
3263
|
+
// ALIGN-EXAMPLE-CASCADE §3.2: variant-level cascade for
|
|
3264
|
+
// quiz choices is delegated to group_trueFalse's
|
|
3265
|
+
// chain-child `@example` (`+` chain with maxEmits:1 +
|
|
3266
|
+
// `@parent.isCorrect=true`). Variant-level `@absent`
|
|
3267
|
+
// would over-fire on subsequent correct choices and on
|
|
3268
|
+
// incorrect ones — drop it.
|
|
3269
|
+
key: ConfigKey.property_example,
|
|
3270
|
+
exportJsonKey: [
|
|
3271
|
+
{ "@keyonly": { isExample: true, "@bit": { isExample: true } } },
|
|
3272
|
+
{ isExample: true, "@bit": { isExample: true } }
|
|
3273
|
+
],
|
|
3274
|
+
description: "Example marker on the quiz choice (entry-local only; cascade handled by group_trueFalse chain-children).",
|
|
3275
|
+
format: TagFormat.boolean,
|
|
3276
|
+
nullable: true
|
|
3277
|
+
}
|
|
3278
|
+
],
|
|
3279
|
+
bodyAllowed: false
|
|
3280
|
+
}
|
|
3281
|
+
]
|
|
3282
|
+
}
|
|
3283
|
+
]
|
|
3284
|
+
},
|
|
3285
|
+
//
|
|
3286
|
+
// quiz (responses)
|
|
3287
|
+
//
|
|
3288
|
+
[CardSetConfigKey.quizResponses]: {
|
|
3289
|
+
jsonKey: "quizzes",
|
|
3290
|
+
exportJsonKey: { quizzes: "$" },
|
|
3291
|
+
sides: [
|
|
3292
|
+
{
|
|
3293
|
+
name: "responses",
|
|
3294
|
+
variants: [
|
|
3295
|
+
{
|
|
3296
|
+
jsonKey: null,
|
|
3297
|
+
tags: [
|
|
3298
|
+
{
|
|
3299
|
+
key: ConfigKey.group_trueFalseResponses,
|
|
3300
|
+
description: "Group for true/false questions."
|
|
3301
|
+
},
|
|
3302
|
+
{
|
|
3303
|
+
key: ConfigKey.group_standardTags,
|
|
3304
|
+
description: "Standard tags for the quiz."
|
|
3305
|
+
},
|
|
3306
|
+
{
|
|
3307
|
+
// PLAN-072: cascade-fired `@example` from the bit header
|
|
3308
|
+
// fills each response entry's `example` with the entry's
|
|
3309
|
+
// own `isCorrect`, mirroring BPG's `pushExampleDownTree`
|
|
3310
|
+
// for the quiz-responses cardset (statements-style).
|
|
3311
|
+
key: ConfigKey.property_example,
|
|
3312
|
+
exportJsonKey: [
|
|
3313
|
+
{ "@keyonly": { isExample: true, "@bit": { isExample: true } } },
|
|
3314
|
+
{
|
|
3315
|
+
"@absent": {
|
|
3316
|
+
isExample: true,
|
|
3317
|
+
example: "$parent.isCorrect",
|
|
3318
|
+
"@bit": { isExample: true }
|
|
3319
|
+
}
|
|
3320
|
+
},
|
|
3321
|
+
{ isExample: true, "@bit": { isExample: true } }
|
|
3322
|
+
],
|
|
3323
|
+
description: "Example marker on the quiz response; cascade from bit-header fills `example` with the entry\u2019s own `isCorrect`.",
|
|
3324
|
+
format: TagFormat.boolean,
|
|
3325
|
+
nullable: true
|
|
2704
3326
|
}
|
|
2705
3327
|
],
|
|
2706
3328
|
bodyAllowed: false
|
|
@@ -2726,6 +3348,73 @@ var CARDSETS = {
|
|
|
2726
3348
|
key: ConfigKey.group_standardItemLeadInstructionHint,
|
|
2727
3349
|
description: "Standard tags for lead, instruction, and hint."
|
|
2728
3350
|
},
|
|
3351
|
+
{
|
|
3352
|
+
// Feedback choices: `[+]` / `[-]` map to choice + `requireReason`
|
|
3353
|
+
// (NOT `isCorrect` like trueFalse). Shadow the inherited
|
|
3354
|
+
// `tag_true` / `tag_false` from `group_trueFalse` to emit the
|
|
3355
|
+
// feedback-specific shape.
|
|
3356
|
+
//
|
|
3357
|
+
// Chain @example: `[+ X][@example]` writes
|
|
3358
|
+
// `isExample/example` onto the X choice only. BPG's
|
|
3359
|
+
// `setIsExampleFlags` (Builder.ts:4041-4172) does NOT
|
|
3360
|
+
// iterate `cardNode.feedbacks` — so unlike multipleChoice,
|
|
3361
|
+
// there is no bubble to bit-level or feedbacks-level
|
|
3362
|
+
// isExample. Pattern omits `@bit` scope-shift accordingly.
|
|
3363
|
+
// No `@absent`/`@parent.isCorrect` cascade rules either:
|
|
3364
|
+
// BPG's `pushExampleDownTree` (Builder.ts:3777-3849) has
|
|
3365
|
+
// no feedback dispatch, so bit-header `[@example]` does
|
|
3366
|
+
// not flow into feedback choices.
|
|
3367
|
+
key: ConfigKey.tag_true,
|
|
3368
|
+
exportJsonKey: { choices: [{ choice: "$", requireReason: true }] },
|
|
3369
|
+
description: "Reason-required choice for feedback (`[+]`).",
|
|
3370
|
+
maxCount: Count.infinity,
|
|
3371
|
+
format: TagFormat.plainText,
|
|
3372
|
+
chain: [
|
|
3373
|
+
{
|
|
3374
|
+
key: ConfigKey.property_example,
|
|
3375
|
+
exportJsonKey: [
|
|
3376
|
+
{ "@keyonly": { isExample: true, example: true } },
|
|
3377
|
+
{ isExample: true, example: "$" }
|
|
3378
|
+
],
|
|
3379
|
+
description: "Per-choice example marker for feedback `[+]`.",
|
|
3380
|
+
format: TagFormat.boolean,
|
|
3381
|
+
maxCount: 1,
|
|
3382
|
+
nullable: true
|
|
3383
|
+
},
|
|
3384
|
+
{
|
|
3385
|
+
// `[+ X][! foo]` chains `!` (instruction) onto the
|
|
3386
|
+
// choice; same for `[?]` (hint), `[%]` (item),
|
|
3387
|
+
// `[%2]`/`[%3]`/`[%4]` (lead/pageNumber/marginNumber).
|
|
3388
|
+
// Mirrors group_trueFalse's `+` chain shape.
|
|
3389
|
+
key: ConfigKey.group_standardItemLeadInstructionHint,
|
|
3390
|
+
description: "Item, lead, page number, margin number, instruction and hint chained onto the feedback choice."
|
|
3391
|
+
}
|
|
3392
|
+
]
|
|
3393
|
+
},
|
|
3394
|
+
{
|
|
3395
|
+
key: ConfigKey.tag_false,
|
|
3396
|
+
exportJsonKey: { choices: [{ choice: "$", requireReason: false }] },
|
|
3397
|
+
description: "Reason-not-required choice for feedback (`[-]`).",
|
|
3398
|
+
maxCount: Count.infinity,
|
|
3399
|
+
format: TagFormat.plainText,
|
|
3400
|
+
chain: [
|
|
3401
|
+
{
|
|
3402
|
+
key: ConfigKey.property_example,
|
|
3403
|
+
exportJsonKey: [
|
|
3404
|
+
{ "@keyonly": { isExample: true, example: true } },
|
|
3405
|
+
{ isExample: true, example: "$" }
|
|
3406
|
+
],
|
|
3407
|
+
description: "Per-choice example marker for feedback `[-]`.",
|
|
3408
|
+
format: TagFormat.boolean,
|
|
3409
|
+
maxCount: 1,
|
|
3410
|
+
nullable: true
|
|
3411
|
+
},
|
|
3412
|
+
{
|
|
3413
|
+
key: ConfigKey.group_standardItemLeadInstructionHint,
|
|
3414
|
+
description: "Item, lead, page number, margin number, instruction and hint chained onto the feedback choice."
|
|
3415
|
+
}
|
|
3416
|
+
]
|
|
3417
|
+
},
|
|
2729
3418
|
{
|
|
2730
3419
|
key: ConfigKey.group_trueFalse,
|
|
2731
3420
|
description: "Group for true/false feedback."
|
|
@@ -2746,31 +3435,71 @@ var CARDSETS = {
|
|
|
2746
3435
|
name: "reason",
|
|
2747
3436
|
variants: [
|
|
2748
3437
|
{
|
|
3438
|
+
// PLAN: feedback.reason — Object-only variant pattern. Per
|
|
3439
|
+
// SYNTAX.md §8.1, sibling tag emissions do NOT auto-wrap
|
|
3440
|
+
// under `reason.*`; each tag here spells out the full path.
|
|
3441
|
+
// Body is a plain string (BPG `parseFeedback` uses
|
|
3442
|
+
// `cardBodyStr ?? ''` for `reason.text`).
|
|
2749
3443
|
jsonKey: "reason.text",
|
|
2750
3444
|
exportJsonKey: { reason: { text: "$" } },
|
|
3445
|
+
format: TextFormat.plainText,
|
|
2751
3446
|
tags: [
|
|
2752
3447
|
{
|
|
2753
|
-
|
|
2754
|
-
|
|
3448
|
+
// Instruction lands under reason. Hint/item/lead/etc.
|
|
3449
|
+
// omitted: not exercised by any fixture and BPG's
|
|
3450
|
+
// `parseFeedback` spreads only the tags actually present
|
|
3451
|
+
// — adding the rest can be done lazily as fixtures
|
|
3452
|
+
// require them, with the same `{reason: {…}}` wrapper.
|
|
3453
|
+
key: ConfigKey.tag_instruction,
|
|
3454
|
+
exportJsonKey: { reason: { instruction: "$" } },
|
|
3455
|
+
description: "Instruction for the feedback reason.",
|
|
3456
|
+
format: TagFormat.bitmarkText
|
|
2755
3457
|
},
|
|
2756
3458
|
{
|
|
2757
3459
|
key: ConfigKey.property_reasonableNumOfChars,
|
|
2758
3460
|
exportJsonKey: [
|
|
2759
|
-
{ "@absent": { reasonableNumOfChars: "$ancestor" } },
|
|
2760
|
-
{ reasonableNumOfChars: "$" }
|
|
3461
|
+
{ "@absent": { reason: { reasonableNumOfChars: "$ancestor" } } },
|
|
3462
|
+
{ reason: { reasonableNumOfChars: "$" } }
|
|
2761
3463
|
],
|
|
2762
3464
|
description: "Property for reasonable number of characters.",
|
|
2763
3465
|
format: TagFormat.number
|
|
2764
3466
|
},
|
|
2765
3467
|
{
|
|
3468
|
+
// ALIGN-EXAMPLE-CASCADE §3.9: `feedback.reason` is NOT in
|
|
3469
|
+
// BPG's `pushExampleDownTree` dispatch — no bit-header
|
|
3470
|
+
// cascade. Local emission only.
|
|
3471
|
+
//
|
|
3472
|
+
// Both `reason.example` and `feedbacks[].example` are
|
|
3473
|
+
// `TextAst` per BPG's schema — paragraph arrays. Format
|
|
3474
|
+
// here is `bitmarkText` so `$` coerces to a paragraph
|
|
3475
|
+
// array at both write sites.
|
|
2766
3476
|
key: ConfigKey.property_example,
|
|
2767
3477
|
exportJsonKey: [
|
|
2768
|
-
{
|
|
2769
|
-
|
|
2770
|
-
|
|
3478
|
+
{
|
|
3479
|
+
// Bare `[@example]`: reason.example is TextAst — BPG
|
|
3480
|
+
// coerces the boolean default to a paragraph
|
|
3481
|
+
// containing the string "true". feedbacks[].example
|
|
3482
|
+
// is not TextAst, stays a boolean. Two literal writes.
|
|
3483
|
+
"@keyonly": {
|
|
3484
|
+
reason: {
|
|
3485
|
+
isExample: true,
|
|
3486
|
+
example: [{ type: "paragraph", content: [{ text: "true", type: "text" }] }]
|
|
3487
|
+
},
|
|
3488
|
+
isExample: true,
|
|
3489
|
+
example: true,
|
|
3490
|
+
"@bit": { isExample: true }
|
|
3491
|
+
}
|
|
3492
|
+
},
|
|
3493
|
+
{
|
|
3494
|
+
reason: { isExample: true, example: "$" },
|
|
3495
|
+
isExample: true,
|
|
3496
|
+
example: "$",
|
|
3497
|
+
"@bit": { isExample: true }
|
|
3498
|
+
}
|
|
2771
3499
|
],
|
|
2772
|
-
description: "Example text for the feedback.",
|
|
2773
|
-
format: TagFormat.
|
|
3500
|
+
description: "Example text for the feedback reason (entry-local only; no bit-header cascade).",
|
|
3501
|
+
format: TagFormat.bitmarkText,
|
|
3502
|
+
nullable: true
|
|
2774
3503
|
},
|
|
2775
3504
|
{
|
|
2776
3505
|
key: ConfigKey.tag_title,
|
|
@@ -2834,6 +3563,39 @@ var CARDSETS = {
|
|
|
2834
3563
|
{
|
|
2835
3564
|
key: ConfigKey.group_standardTags,
|
|
2836
3565
|
description: "Standard tags for the question."
|
|
3566
|
+
},
|
|
3567
|
+
{
|
|
3568
|
+
// ALIGN-EXAMPLE-CASCADE §3.10: `questions` is NOT in BPG's
|
|
3569
|
+
// `pushExampleDownTree` dispatch — bit-header `[@example]`
|
|
3570
|
+
// does not propagate `example`/`isExample` onto question
|
|
3571
|
+
// entries. Only entry-local `[@example]` fires.
|
|
3572
|
+
//
|
|
3573
|
+
// BPG's `BaseBuilder.toExample` for interview questions
|
|
3574
|
+
// (Builder.ts:2575,2588) uses `data.__sampleSolutionAst`
|
|
3575
|
+
// as `defaultExample` — bare `[@example]` (which sets
|
|
3576
|
+
// `__isDefaultExample=true, example=undefined`) emits the
|
|
3577
|
+
// parsed sample-solution AST in the `example` slot. The
|
|
3578
|
+
// `@keyonly` rule mirrors that runtime behaviour via
|
|
3579
|
+
// `$parent.sampleSolution` — the chain-walker's post-walk
|
|
3580
|
+
// retry resolves this once the sibling `[@sampleSolution]`
|
|
3581
|
+
// chain has merged into the shared scope (interview's
|
|
3582
|
+
// `[@example]` typically precedes `[@sampleSolution]` in
|
|
3583
|
+
// source). Format coercion turns the resolved string
|
|
3584
|
+
// value into a PM tree.
|
|
3585
|
+
key: ConfigKey.property_example,
|
|
3586
|
+
exportJsonKey: [
|
|
3587
|
+
{
|
|
3588
|
+
"@keyonly": {
|
|
3589
|
+
isExample: true,
|
|
3590
|
+
example: "$parent.sampleSolution",
|
|
3591
|
+
"@bit": { isExample: true }
|
|
3592
|
+
}
|
|
3593
|
+
},
|
|
3594
|
+
{ isExample: true, example: "$", "@bit": { isExample: true } }
|
|
3595
|
+
],
|
|
3596
|
+
description: "Example marker for the question (entry-local only; no bit-header cascade).",
|
|
3597
|
+
format: TagFormat.bitmarkText,
|
|
3598
|
+
nullable: true
|
|
2837
3599
|
}
|
|
2838
3600
|
]
|
|
2839
3601
|
}
|
|
@@ -2860,14 +3622,16 @@ var CARDSETS = {
|
|
|
2860
3622
|
description: "Standard tags for lead, instruction, and hint."
|
|
2861
3623
|
},
|
|
2862
3624
|
{
|
|
3625
|
+
// ALIGN-EXAMPLE-CASCADE §3.11: `elements` is NOT in BPG's
|
|
3626
|
+
// `pushExampleDownTree` dispatch — no bit-header cascade.
|
|
2863
3627
|
key: ConfigKey.property_example,
|
|
2864
3628
|
exportJsonKey: [
|
|
2865
3629
|
{ "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2866
|
-
{ "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
2867
3630
|
{ isExample: true, example: "$", "@bit": { isExample: true } }
|
|
2868
3631
|
],
|
|
2869
|
-
description: "Example text for the element.",
|
|
2870
|
-
format: TagFormat.plainText
|
|
3632
|
+
description: "Example text for the element (entry-local only; no bit-header cascade).",
|
|
3633
|
+
format: TagFormat.plainText,
|
|
3634
|
+
nullable: true
|
|
2871
3635
|
}
|
|
2872
3636
|
],
|
|
2873
3637
|
repeatCount: Count.infinity
|
|
@@ -2879,9 +3643,57 @@ var CARDSETS = {
|
|
|
2879
3643
|
//
|
|
2880
3644
|
// table
|
|
2881
3645
|
//
|
|
3646
|
+
// PLAN-079: supports BOTH source formats and collapses to the same basic
|
|
3647
|
+
// output (`{ columns?, data, columnWidths? }`):
|
|
3648
|
+
//
|
|
3649
|
+
// 1. Basic format — first card carries `[#X]` markers (fire `tag_title`
|
|
3650
|
+
// → `^table.columns[]`); subsequent cards are body rows. Cards have
|
|
3651
|
+
// no qualifier, so they route to the `table-body` (default) section.
|
|
3652
|
+
//
|
|
3653
|
+
// 2. Extended format — `==== table-header ====` / `==== table-footer ====`
|
|
3654
|
+
// qualifiers. The `table-header` section uses `@cardIndex=0` to route
|
|
3655
|
+
// the first header card's cells to `columns` and demote remaining
|
|
3656
|
+
// header cards (rows) into `data`. Per-cell `[@tableRowSpan]` /
|
|
3657
|
+
// `[@tableScope]` / `[@tableCellType]` tags are valid-but-unmapped
|
|
3658
|
+
// here (no per-cell metadata in basic shape) — declared on the cell
|
|
3659
|
+
// variant below.
|
|
3660
|
+
//
|
|
2882
3661
|
[CardSetConfigKey.table]: {
|
|
2883
|
-
jsonKey:
|
|
2884
|
-
exportJsonKey:
|
|
3662
|
+
jsonKey: null,
|
|
3663
|
+
exportJsonKey: null,
|
|
3664
|
+
sections: {
|
|
3665
|
+
"table-header": {
|
|
3666
|
+
// First header card's row of cells → `columns`. Subsequent header
|
|
3667
|
+
// cards' rows → `data` (per-card append; `[$]` shape because
|
|
3668
|
+
// per-card emission fires once per card, multiple fires deep_merge-
|
|
3669
|
+
// append). PLAN-079 + PLAN-080: predicates inside exportJsonKey
|
|
3670
|
+
// both ROUTE cards to this section (when no explicit `==== table-
|
|
3671
|
+
// header ====` qualifier) and SELECT the rule body once routed.
|
|
3672
|
+
// First rule = first card with any `[#]` → `columns`; second rule
|
|
3673
|
+
// = subsequent cards in this section → `data` (per-row append).
|
|
3674
|
+
jsonKey: "table.columns",
|
|
3675
|
+
exportJsonKey: [
|
|
3676
|
+
{
|
|
3677
|
+
predicates: [{ "@cardIndex": 0 }, { "@variantHasTag": "#" }],
|
|
3678
|
+
rule: { "@bit": { table: { columns: "$" } } }
|
|
3679
|
+
},
|
|
3680
|
+
{ "@cardIndex=0": { "@bit": { table: { columns: "$" } } } },
|
|
3681
|
+
{ "@bit": { table: { data: ["$"] } } }
|
|
3682
|
+
]
|
|
3683
|
+
},
|
|
3684
|
+
"table-body": {
|
|
3685
|
+
// Batched emission: `$` is the entire 2D array of body card rows.
|
|
3686
|
+
// This is the default section (no qualifier → cards land here).
|
|
3687
|
+
jsonKey: "table.data",
|
|
3688
|
+
exportJsonKey: { "@bit": { table: { data: "$" } } },
|
|
3689
|
+
isDefault: true
|
|
3690
|
+
},
|
|
3691
|
+
"table-footer": {
|
|
3692
|
+
// Batched emission: `$` is the entire 2D array of footer rows.
|
|
3693
|
+
jsonKey: "table.data",
|
|
3694
|
+
exportJsonKey: { "@bit": { table: { data: "$" } } }
|
|
3695
|
+
}
|
|
3696
|
+
},
|
|
2885
3697
|
sides: [
|
|
2886
3698
|
{
|
|
2887
3699
|
name: "cell",
|
|
@@ -2922,6 +3734,8 @@ var CARDSETS = {
|
|
|
2922
3734
|
},
|
|
2923
3735
|
{
|
|
2924
3736
|
key: ConfigKey.property_tableColWidth,
|
|
3737
|
+
jsonKey: "^table.columnWidths[{s}]",
|
|
3738
|
+
exportJsonKey: { "@bit": { table: { columnWidths: { $s: "$" } } } },
|
|
2925
3739
|
description: "Width for the column.",
|
|
2926
3740
|
format: TagFormat.number
|
|
2927
3741
|
}
|
|
@@ -2940,7 +3754,22 @@ var CARDSETS = {
|
|
|
2940
3754
|
sections: {
|
|
2941
3755
|
"table-header": {
|
|
2942
3756
|
jsonKey: "table.header.rows",
|
|
2943
|
-
|
|
3757
|
+
// PLAN-080: section dispatch via predicates inside exportJsonKey.
|
|
3758
|
+
// Cards with no explicit `==== table-header ====` qualifier
|
|
3759
|
+
// route here if FIRST CARD contains any `[#]` tag in any
|
|
3760
|
+
// variant. Explicit-qualifier cards fall through to the
|
|
3761
|
+
// unconditional fallback rule. Both rules use the per-card
|
|
3762
|
+
// append shape `rows: ['$']` — the runtime triggers per-card
|
|
3763
|
+
// emission whenever the pattern carries predicates, and each
|
|
3764
|
+
// fire appends a single-element array (multi-fire deep-merge
|
|
3765
|
+
// concatenates).
|
|
3766
|
+
exportJsonKey: [
|
|
3767
|
+
{
|
|
3768
|
+
predicates: [{ "@cardIndex": 0 }, { "@variantHasTag": "#" }],
|
|
3769
|
+
rule: { table: { header: { rows: ["$"] } } }
|
|
3770
|
+
},
|
|
3771
|
+
{ table: { header: { rows: ["$"] } } }
|
|
3772
|
+
],
|
|
2944
3773
|
sideJsonKey: "cells[{s}]|set(title=true)",
|
|
2945
3774
|
sideExportJsonKey: { cells: { $s: { title: true, $: "$" } } }
|
|
2946
3775
|
},
|
|
@@ -2973,13 +3802,13 @@ var CARDSETS = {
|
|
|
2973
3802
|
},
|
|
2974
3803
|
{
|
|
2975
3804
|
key: ConfigKey.tag_title,
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
},
|
|
2982
|
-
description: "Title of the table cell."
|
|
3805
|
+
// PLAN-080: the `[#]` tag writes its text to the cell's
|
|
3806
|
+
// `content` field. When the card is in the `table-header`
|
|
3807
|
+
// section, the section's sideExportJsonKey wraps each
|
|
3808
|
+
// cell with `title: true` automatically.
|
|
3809
|
+
jsonKey: "content",
|
|
3810
|
+
exportJsonKey: { content: "$" },
|
|
3811
|
+
description: "Title text of the table cell (header rows)."
|
|
2983
3812
|
},
|
|
2984
3813
|
{
|
|
2985
3814
|
key: ConfigKey.property_tableCellType,
|
|
@@ -3011,6 +3840,9 @@ var CARDSETS = {
|
|
|
3011
3840
|
},
|
|
3012
3841
|
{
|
|
3013
3842
|
key: ConfigKey.property_tableColWidth,
|
|
3843
|
+
// PLAN-080: write to the cell's `colwidth` field.
|
|
3844
|
+
jsonKey: "colwidth",
|
|
3845
|
+
exportJsonKey: { colwidth: "$" },
|
|
3014
3846
|
description: "Width for the column.",
|
|
3015
3847
|
format: TagFormat.number
|
|
3016
3848
|
}
|
|
@@ -3048,7 +3880,8 @@ var CARDSETS = {
|
|
|
3048
3880
|
{
|
|
3049
3881
|
key: ConfigKey.resource_audio,
|
|
3050
3882
|
description: "Audio resource for the pronunciation table.",
|
|
3051
|
-
jsonKey: "audio"
|
|
3883
|
+
jsonKey: "audio",
|
|
3884
|
+
exportJsonKey: { audio: { type: "audio", audio: { src: "$" } } }
|
|
3052
3885
|
}
|
|
3053
3886
|
],
|
|
3054
3887
|
repeatCount: Count.infinity
|
|
@@ -3119,14 +3952,17 @@ var CARDSETS = {
|
|
|
3119
3952
|
format: TagFormat.plainText
|
|
3120
3953
|
},
|
|
3121
3954
|
{
|
|
3955
|
+
// ALIGN-EXAMPLE-CASCADE §3.12: `bot-action-responses` is
|
|
3956
|
+
// NOT in BPG's `pushExampleDownTree` dispatch — no bit-
|
|
3957
|
+
// header cascade.
|
|
3122
3958
|
key: ConfigKey.property_example,
|
|
3123
3959
|
exportJsonKey: [
|
|
3124
3960
|
{ "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
3125
|
-
{ "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
|
|
3126
3961
|
{ isExample: true, example: "$", "@bit": { isExample: true } }
|
|
3127
3962
|
],
|
|
3128
|
-
description: "Example text for the bot action response.",
|
|
3129
|
-
format: TagFormat.plainText
|
|
3963
|
+
description: "Example text for the bot action response (entry-local only; no bit-header cascade).",
|
|
3964
|
+
format: TagFormat.plainText,
|
|
3965
|
+
nullable: true
|
|
3130
3966
|
}
|
|
3131
3967
|
]
|
|
3132
3968
|
}
|
|
@@ -3192,6 +4028,38 @@ var CARDSETS = {
|
|
|
3192
4028
|
]
|
|
3193
4029
|
},
|
|
3194
4030
|
//
|
|
4031
|
+
// page-footer-sections — mirrors example-bit-list but emits cards under
|
|
4032
|
+
// `sections` instead of `listItems`. BPG's JsonGenerator special-cases
|
|
4033
|
+
// BitType.pageFooter in code; this dedicated cardSet expresses the same
|
|
4034
|
+
// routing as pure config so the Rust serializer can pick it up.
|
|
4035
|
+
//
|
|
4036
|
+
[CardSetConfigKey.pageFooterSections]: {
|
|
4037
|
+
jsonKey: "sections",
|
|
4038
|
+
exportJsonKey: { sections: "$" },
|
|
4039
|
+
sides: [
|
|
4040
|
+
{
|
|
4041
|
+
name: "item",
|
|
4042
|
+
variants: [
|
|
4043
|
+
{
|
|
4044
|
+
jsonKey: "body",
|
|
4045
|
+
exportJsonKey: { body: "$" },
|
|
4046
|
+
tags: [
|
|
4047
|
+
{
|
|
4048
|
+
key: ConfigKey.group_standardTags,
|
|
4049
|
+
description: "Standard tags for page-footer section bits."
|
|
4050
|
+
},
|
|
4051
|
+
{
|
|
4052
|
+
exportJsonKey: { title: "$" },
|
|
4053
|
+
key: ConfigKey.tag_title,
|
|
4054
|
+
description: "Title of the page-footer section."
|
|
4055
|
+
}
|
|
4056
|
+
]
|
|
4057
|
+
}
|
|
4058
|
+
]
|
|
4059
|
+
}
|
|
4060
|
+
]
|
|
4061
|
+
},
|
|
4062
|
+
//
|
|
3195
4063
|
// ingredients
|
|
3196
4064
|
//
|
|
3197
4065
|
[CardSetConfigKey.ingredients]: {
|
|
@@ -3522,7 +4390,11 @@ var GROUPS = {
|
|
|
3522
4390
|
{
|
|
3523
4391
|
key: ConfigKey.property_groupTag,
|
|
3524
4392
|
jsonKey: "groupTag.name",
|
|
3525
|
-
|
|
4393
|
+
// PLAN-063: starter-array shape with `@id` declares entity-merge
|
|
4394
|
+
// semantics — repeated `[@groupTag: name]` occurrences with the
|
|
4395
|
+
// same value fold into one entry; chained `@tag` children's
|
|
4396
|
+
// arrays merge as sets.
|
|
4397
|
+
exportJsonKey: { groupTag: [{ "@id": "name", name: "$" }] },
|
|
3526
4398
|
description: "The group tag(s) for the bit",
|
|
3527
4399
|
format: TagFormat.plainText,
|
|
3528
4400
|
maxCount: Count.infinity,
|
|
@@ -3695,6 +4567,24 @@ var GROUPS = {
|
|
|
3695
4567
|
}
|
|
3696
4568
|
]
|
|
3697
4569
|
},
|
|
4570
|
+
[ConfigKey.group_standardInstructionHint]: {
|
|
4571
|
+
type: GroupConfigType.standard,
|
|
4572
|
+
description: "Standard group for instruction and hint tags only (no item/lead/pageNumber/marginNumber chain). Used by sides where item/lead are owned by another side (e.g. matchPairs value-side, which BPG strips item/lead from at sideIdx > 0).",
|
|
4573
|
+
tags: [
|
|
4574
|
+
{
|
|
4575
|
+
exportJsonKey: { instruction: "$" },
|
|
4576
|
+
key: ConfigKey.tag_instruction,
|
|
4577
|
+
name: "Instruction",
|
|
4578
|
+
description: "The instruction for the bit"
|
|
4579
|
+
},
|
|
4580
|
+
{
|
|
4581
|
+
exportJsonKey: { hint: "$" },
|
|
4582
|
+
key: ConfigKey.tag_hint,
|
|
4583
|
+
name: "Hint",
|
|
4584
|
+
description: "The hint for the bit"
|
|
4585
|
+
}
|
|
4586
|
+
]
|
|
4587
|
+
},
|
|
3698
4588
|
[ConfigKey.group_standardTags]: {
|
|
3699
4589
|
type: GroupConfigType.standard,
|
|
3700
4590
|
description: "Standard tags which apply to MOST (but not all) bits",
|
|
@@ -3708,17 +4598,45 @@ var GROUPS = {
|
|
|
3708
4598
|
description: "Item, lead, page number, margin number, instruction and hint tags"
|
|
3709
4599
|
},
|
|
3710
4600
|
{
|
|
3711
|
-
// Bit-level example tag
|
|
3712
|
-
//
|
|
3713
|
-
//
|
|
4601
|
+
// Bit-level example tag (default — non-`isTopLevelExample` bits):
|
|
4602
|
+
// - bare `[@example]`: consume only (records cascade marker, emits
|
|
4603
|
+
// nothing at bit-level — OLD parser scrubs bit-level isExample
|
|
4604
|
+
// for bits NOT in its `isTopLevelExample` allow-list).
|
|
4605
|
+
// - synthesized (descendant cascade): still emits `isExample: true`
|
|
4606
|
+
// at the descendant scope.
|
|
4607
|
+
// - valued `[@example:text]`: emit the example value only; bit-level
|
|
4608
|
+
// `isExample` is left to bubble from descendants.
|
|
4609
|
+
// Bits in the OLD parser's allow-list (flashcard, definitionList,
|
|
4610
|
+
// sequence, multipleChoice, trueFalse, cloze*, mark, interview,
|
|
4611
|
+
// matchMatrix, match, essay, etc.) override this entry with the
|
|
4612
|
+
// legacy emit-isExample shape.
|
|
3714
4613
|
key: ConfigKey.property_example,
|
|
3715
|
-
exportJsonKey: [
|
|
3716
|
-
{ "@keyonly": { isExample: true } },
|
|
3717
|
-
{ "@absent": { isExample: true } },
|
|
3718
|
-
{ isExample: true, example: "$" }
|
|
3719
|
-
],
|
|
4614
|
+
exportJsonKey: [{ "@keyonly": {} }, { "@absent": { isExample: true } }, { example: "$" }],
|
|
3720
4615
|
description: "The example(s) for the bit",
|
|
3721
|
-
format: TagFormat.bitmarkText
|
|
4616
|
+
format: TagFormat.bitmarkText,
|
|
4617
|
+
nullable: true
|
|
4618
|
+
}
|
|
4619
|
+
]
|
|
4620
|
+
},
|
|
4621
|
+
[ConfigKey.group_standardTagsNoItemLead]: {
|
|
4622
|
+
type: GroupConfigType.standard,
|
|
4623
|
+
description: "Standard tags mirror of group_standardTags but excluding the item/lead/pageNumber/marginNumber chain. Used by sides where item/lead are owned by another side (e.g. matchPairs value-side \u2014 BPG `parseMatchPairs` strips tags.item / tags.lead for sideIdx > 0).",
|
|
4624
|
+
tags: [
|
|
4625
|
+
{
|
|
4626
|
+
key: ConfigKey.group_standardAllBits,
|
|
4627
|
+
description: "All standard tags which apply to all bits"
|
|
4628
|
+
},
|
|
4629
|
+
{
|
|
4630
|
+
key: ConfigKey.group_standardInstructionHint,
|
|
4631
|
+
description: "Instruction and hint tags only (no item/lead chain)."
|
|
4632
|
+
},
|
|
4633
|
+
{
|
|
4634
|
+
// Mirror of group_standardTags property_example entry.
|
|
4635
|
+
key: ConfigKey.property_example,
|
|
4636
|
+
exportJsonKey: [{ "@keyonly": {} }, { "@absent": { isExample: true } }, { example: "$" }],
|
|
4637
|
+
description: "The example(s) for the bit",
|
|
4638
|
+
format: TagFormat.bitmarkText,
|
|
4639
|
+
nullable: true
|
|
3722
4640
|
}
|
|
3723
4641
|
]
|
|
3724
4642
|
},
|
|
@@ -3783,8 +4701,8 @@ var GROUPS = {
|
|
|
3783
4701
|
tags: [
|
|
3784
4702
|
{
|
|
3785
4703
|
key: ConfigKey.property_person,
|
|
3786
|
-
jsonKey: "
|
|
3787
|
-
exportJsonKey: {
|
|
4704
|
+
jsonKey: "person.name",
|
|
4705
|
+
exportJsonKey: { person: { name: "$" } },
|
|
3788
4706
|
description: "A person",
|
|
3789
4707
|
format: TagFormat.plainText,
|
|
3790
4708
|
chain: [
|
|
@@ -3804,8 +4722,8 @@ var GROUPS = {
|
|
|
3804
4722
|
{
|
|
3805
4723
|
// Deprecated (parter renamed to person)
|
|
3806
4724
|
key: ConfigKey.property_partner,
|
|
3807
|
-
jsonKey: "
|
|
3808
|
-
exportJsonKey: {
|
|
4725
|
+
jsonKey: "person.name",
|
|
4726
|
+
exportJsonKey: { person: { name: "$" } },
|
|
3809
4727
|
description: "A partner",
|
|
3810
4728
|
format: TagFormat.plainText,
|
|
3811
4729
|
chain: [
|
|
@@ -3829,7 +4747,9 @@ var GROUPS = {
|
|
|
3829
4747
|
description: "Gap chain",
|
|
3830
4748
|
tags: [
|
|
3831
4749
|
{
|
|
3832
|
-
exportJsonKey: {
|
|
4750
|
+
exportJsonKey: {
|
|
4751
|
+
"@body-inline": { type: "gap", attrs: { solutions: ["$"] } }
|
|
4752
|
+
},
|
|
3833
4753
|
key: ConfigKey.tag_gap,
|
|
3834
4754
|
description: "The value of a gap in the content",
|
|
3835
4755
|
maxCount: Count.infinity,
|
|
@@ -3848,27 +4768,37 @@ var GROUPS = {
|
|
|
3848
4768
|
description: "Item, lead, page number, margin number, instruction and hint tags"
|
|
3849
4769
|
},
|
|
3850
4770
|
{
|
|
4771
|
+
// BPG ExampleTagContentProcessor.ts:91-95 reads
|
|
4772
|
+
// `__solutionsAst[length - 1]` — the solution closest to the
|
|
4773
|
+
// `[@example]` tag at parse time. Our chain accumulator is built
|
|
4774
|
+
// incrementally (scope.rs `parent.merge` after each tag), so at
|
|
4775
|
+
// the moment `[@example]` fires, `solutions` only contains the
|
|
4776
|
+
// preceding `[_x]` entries — `[-1]` resolves to the closest one.
|
|
4777
|
+
// E.g. `[_cloze][_gap][@example]` → solutions=["cloze","gap"],
|
|
4778
|
+
// `[-1]`="gap"; `[_solution][@example][_problem]` →
|
|
4779
|
+
// solutions=["solution"], `[-1]`="solution".
|
|
3851
4780
|
key: ConfigKey.property_example,
|
|
3852
4781
|
jsonKey: "example",
|
|
3853
4782
|
exportJsonKey: [
|
|
3854
4783
|
{
|
|
3855
4784
|
"@keyonly": {
|
|
3856
4785
|
isExample: true,
|
|
3857
|
-
example: "$parent.solutions[
|
|
4786
|
+
example: "$parent.solutions[-1]",
|
|
3858
4787
|
"@bit": { isExample: true }
|
|
3859
4788
|
}
|
|
3860
4789
|
},
|
|
3861
4790
|
{
|
|
3862
4791
|
"@absent": {
|
|
3863
4792
|
isExample: true,
|
|
3864
|
-
example: "$parent.solutions[
|
|
4793
|
+
example: "$parent.solutions[-1]",
|
|
3865
4794
|
"@bit": { isExample: true }
|
|
3866
4795
|
}
|
|
3867
4796
|
},
|
|
3868
4797
|
{ isExample: true, example: "$", "@bit": { isExample: true } }
|
|
3869
4798
|
],
|
|
3870
4799
|
description: "An example for the gap",
|
|
3871
|
-
format: TagFormat.bitmarkText
|
|
4800
|
+
format: TagFormat.bitmarkText,
|
|
4801
|
+
nullable: true
|
|
3872
4802
|
},
|
|
3873
4803
|
{
|
|
3874
4804
|
key: ConfigKey.property_isCaseSensitive,
|
|
@@ -3917,6 +4847,99 @@ var GROUPS = {
|
|
|
3917
4847
|
description: "Item, lead, page number, margin number, instruction and hint tags"
|
|
3918
4848
|
},
|
|
3919
4849
|
{
|
|
4850
|
+
// PLAN-072 + ALIGN-EXAMPLE-CASCADE §3.2: cascade-fired
|
|
4851
|
+
// `@example` from the bit header marks EVERY choice with
|
|
4852
|
+
// `isExample: true` (matches BPG output), but only the
|
|
4853
|
+
// FIRST correct choice gets `example: true` (BPG
|
|
4854
|
+
// `fillBooleanExample(..., firstCorrectOnly=true)`).
|
|
4855
|
+
//
|
|
4856
|
+
// Two `@absent` rules, picked first-match-wins:
|
|
4857
|
+
// 1. Verbose form gated on `@parent.isCorrect=true` with
|
|
4858
|
+
// `maxEmits:1` — fires once for the first correct.
|
|
4859
|
+
// 2. Plain `@absent` — fires on every remaining entry
|
|
4860
|
+
// (subsequent corrects and all incorrects), emitting
|
|
4861
|
+
// `isExample` only.
|
|
4862
|
+
// The `serialize_cards` cascade-counter scope holds the cap
|
|
4863
|
+
// across the whole cardset.
|
|
4864
|
+
// @card scope-shift writes choice-container isExample (e.g.
|
|
4865
|
+
// `quizzes[i].isExample`) — was previously provided by the
|
|
4866
|
+
// universal `bubble_is_example` post-walk, which was removed
|
|
4867
|
+
// in favour of explicit per-tag config. SYNTAX.md §8.
|
|
4868
|
+
key: ConfigKey.property_example,
|
|
4869
|
+
jsonKey: "example",
|
|
4870
|
+
exportJsonKey: [
|
|
4871
|
+
{
|
|
4872
|
+
"@keyonly": {
|
|
4873
|
+
isExample: true,
|
|
4874
|
+
example: "$parent.isCorrect",
|
|
4875
|
+
"@bit": { isExample: true },
|
|
4876
|
+
"@card": { isExample: true }
|
|
4877
|
+
}
|
|
4878
|
+
},
|
|
4879
|
+
{
|
|
4880
|
+
predicates: ["@absent", { "@parent.isCorrect": true }],
|
|
4881
|
+
maxEmits: 1,
|
|
4882
|
+
rule: {
|
|
4883
|
+
isExample: true,
|
|
4884
|
+
example: true,
|
|
4885
|
+
"@bit": { isExample: true },
|
|
4886
|
+
"@card": { isExample: true }
|
|
4887
|
+
}
|
|
4888
|
+
},
|
|
4889
|
+
{
|
|
4890
|
+
"@absent": {
|
|
4891
|
+
isExample: true,
|
|
4892
|
+
"@bit": { isExample: true },
|
|
4893
|
+
"@card": { isExample: true }
|
|
4894
|
+
}
|
|
4895
|
+
},
|
|
4896
|
+
{
|
|
4897
|
+
isExample: true,
|
|
4898
|
+
example: "$",
|
|
4899
|
+
"@bit": { isExample: true },
|
|
4900
|
+
"@card": { isExample: true }
|
|
4901
|
+
}
|
|
4902
|
+
],
|
|
4903
|
+
description: "An example for the true/false statement/question",
|
|
4904
|
+
format: TagFormat.boolean,
|
|
4905
|
+
nullable: true
|
|
4906
|
+
}
|
|
4907
|
+
]
|
|
4908
|
+
},
|
|
4909
|
+
{
|
|
4910
|
+
exportJsonKey: { choices: [{ choice: "$", isCorrect: false }] },
|
|
4911
|
+
key: ConfigKey.tag_false,
|
|
4912
|
+
description: "False value for a true/false statement/question",
|
|
4913
|
+
maxCount: Count.infinity,
|
|
4914
|
+
format: TagFormat.plainText,
|
|
4915
|
+
chain: [
|
|
4916
|
+
{
|
|
4917
|
+
key: ConfigKey.tag_true,
|
|
4918
|
+
jsonKey: "choices[]|set(isCorrect=true)",
|
|
4919
|
+
exportJsonKey: { choices: [{ choice: "$", isCorrect: true }] },
|
|
4920
|
+
description: "True values for a true/false statement/question",
|
|
4921
|
+
maxCount: Count.infinity,
|
|
4922
|
+
format: TagFormat.plainText
|
|
4923
|
+
},
|
|
4924
|
+
{
|
|
4925
|
+
key: ConfigKey.tag_false,
|
|
4926
|
+
jsonKey: "choices[]|set(isCorrect=false)",
|
|
4927
|
+
exportJsonKey: { choices: [{ choice: "$", isCorrect: false }] },
|
|
4928
|
+
description: "False values for a true/false statement/question",
|
|
4929
|
+
maxCount: Count.infinity,
|
|
4930
|
+
format: TagFormat.plainText
|
|
4931
|
+
},
|
|
4932
|
+
{
|
|
4933
|
+
key: ConfigKey.group_standardItemLeadInstructionHint,
|
|
4934
|
+
description: "Item, lead, page number, margin number, instruction and hint tags"
|
|
4935
|
+
},
|
|
4936
|
+
{
|
|
4937
|
+
// PLAN-072 + ALIGN-EXAMPLE-CASCADE §3.2: cascade-fired
|
|
4938
|
+
// `@example` on a `-` (incorrect choice) emits
|
|
4939
|
+
// `isExample: true` only — no `example` (BPG fixtures show
|
|
4940
|
+
// `isExample` on every choice, but `example` only on the
|
|
4941
|
+
// first correct via firstCorrectOnly cascade).
|
|
4942
|
+
// @card scope-shift writes choice-container isExample.
|
|
3920
4943
|
key: ConfigKey.property_example,
|
|
3921
4944
|
jsonKey: "example",
|
|
3922
4945
|
exportJsonKey: [
|
|
@@ -3924,25 +4947,99 @@ var GROUPS = {
|
|
|
3924
4947
|
"@keyonly": {
|
|
3925
4948
|
isExample: true,
|
|
3926
4949
|
example: "$parent.isCorrect",
|
|
3927
|
-
"@bit": { isExample: true }
|
|
4950
|
+
"@bit": { isExample: true },
|
|
4951
|
+
"@card": { isExample: true }
|
|
3928
4952
|
}
|
|
3929
4953
|
},
|
|
3930
4954
|
{
|
|
3931
4955
|
"@absent": {
|
|
4956
|
+
isExample: true,
|
|
4957
|
+
"@bit": { isExample: true },
|
|
4958
|
+
"@card": { isExample: true }
|
|
4959
|
+
}
|
|
4960
|
+
},
|
|
4961
|
+
{
|
|
4962
|
+
isExample: true,
|
|
4963
|
+
example: "$",
|
|
4964
|
+
"@bit": { isExample: true },
|
|
4965
|
+
"@card": { isExample: true }
|
|
4966
|
+
}
|
|
4967
|
+
],
|
|
4968
|
+
description: "An example for the true/false statement/question (incorrect entry \u2014 isExample only on cascade)",
|
|
4969
|
+
format: TagFormat.boolean,
|
|
4970
|
+
nullable: true
|
|
4971
|
+
}
|
|
4972
|
+
]
|
|
4973
|
+
}
|
|
4974
|
+
]
|
|
4975
|
+
},
|
|
4976
|
+
[ConfigKey.group_trueFalseResponses]: {
|
|
4977
|
+
type: GroupConfigType.standard,
|
|
4978
|
+
description: "True/False Responses chain",
|
|
4979
|
+
tags: [
|
|
4980
|
+
{
|
|
4981
|
+
exportJsonKey: { responses: [{ response: "$", isCorrect: true }] },
|
|
4982
|
+
key: ConfigKey.tag_true,
|
|
4983
|
+
description: "True value for a true/false statement/question",
|
|
4984
|
+
maxCount: Count.infinity,
|
|
4985
|
+
format: TagFormat.plainText,
|
|
4986
|
+
chain: [
|
|
4987
|
+
{
|
|
4988
|
+
key: ConfigKey.tag_true,
|
|
4989
|
+
jsonKey: "responses[]|set(isCorrect=true)",
|
|
4990
|
+
exportJsonKey: { responses: [{ response: "$", isCorrect: true }] },
|
|
4991
|
+
description: "True values for a true/false statement/question",
|
|
4992
|
+
maxCount: Count.infinity,
|
|
4993
|
+
format: TagFormat.plainText
|
|
4994
|
+
},
|
|
4995
|
+
{
|
|
4996
|
+
key: ConfigKey.tag_false,
|
|
4997
|
+
jsonKey: "responses[]|set(isCorrect=false)",
|
|
4998
|
+
exportJsonKey: { responses: [{ response: "$", isCorrect: false }] },
|
|
4999
|
+
description: "False values for a true/false statement/question",
|
|
5000
|
+
maxCount: Count.infinity,
|
|
5001
|
+
format: TagFormat.plainText
|
|
5002
|
+
},
|
|
5003
|
+
{
|
|
5004
|
+
key: ConfigKey.group_standardItemLeadInstructionHint,
|
|
5005
|
+
description: "Item, lead, page number, margin number, instruction and hint tags"
|
|
5006
|
+
},
|
|
5007
|
+
{
|
|
5008
|
+
// @card scope-shift writes response-container isExample.
|
|
5009
|
+
key: ConfigKey.property_example,
|
|
5010
|
+
jsonKey: "example",
|
|
5011
|
+
exportJsonKey: [
|
|
5012
|
+
{
|
|
5013
|
+
"@keyonly": {
|
|
3932
5014
|
isExample: true,
|
|
3933
5015
|
example: "$parent.isCorrect",
|
|
3934
|
-
"@bit": { isExample: true }
|
|
5016
|
+
"@bit": { isExample: true },
|
|
5017
|
+
"@card": { isExample: true }
|
|
3935
5018
|
}
|
|
3936
5019
|
},
|
|
3937
|
-
{
|
|
5020
|
+
{
|
|
5021
|
+
"@absent": {
|
|
5022
|
+
isExample: "$parent.isCorrect",
|
|
5023
|
+
example: "$parent.isCorrect",
|
|
5024
|
+
"@bit": { isExample: "$parent.isCorrect" },
|
|
5025
|
+
"@card": { isExample: "$parent.isCorrect" }
|
|
5026
|
+
}
|
|
5027
|
+
},
|
|
5028
|
+
{
|
|
5029
|
+
isExample: true,
|
|
5030
|
+
example: "$",
|
|
5031
|
+
"@bit": { isExample: true },
|
|
5032
|
+
"@card": { isExample: true }
|
|
5033
|
+
}
|
|
3938
5034
|
],
|
|
3939
5035
|
description: "An example for the true/false statement/question",
|
|
3940
|
-
format: TagFormat.
|
|
5036
|
+
format: TagFormat.boolean,
|
|
5037
|
+
nullable: true
|
|
3941
5038
|
}
|
|
3942
5039
|
]
|
|
3943
5040
|
},
|
|
3944
5041
|
{
|
|
3945
|
-
exportJsonKey: {
|
|
5042
|
+
exportJsonKey: { responses: [{ response: "$", isCorrect: false }] },
|
|
3946
5043
|
key: ConfigKey.tag_false,
|
|
3947
5044
|
description: "False value for a true/false statement/question",
|
|
3948
5045
|
maxCount: Count.infinity,
|
|
@@ -3950,16 +5047,16 @@ var GROUPS = {
|
|
|
3950
5047
|
chain: [
|
|
3951
5048
|
{
|
|
3952
5049
|
key: ConfigKey.tag_true,
|
|
3953
|
-
jsonKey: "
|
|
3954
|
-
exportJsonKey: {
|
|
5050
|
+
jsonKey: "responses[]|set(isCorrect=true)",
|
|
5051
|
+
exportJsonKey: { responses: [{ response: "$", isCorrect: true }] },
|
|
3955
5052
|
description: "True values for a true/false statement/question",
|
|
3956
5053
|
maxCount: Count.infinity,
|
|
3957
5054
|
format: TagFormat.plainText
|
|
3958
5055
|
},
|
|
3959
5056
|
{
|
|
3960
5057
|
key: ConfigKey.tag_false,
|
|
3961
|
-
jsonKey: "
|
|
3962
|
-
exportJsonKey: {
|
|
5058
|
+
jsonKey: "responses[]|set(isCorrect=false)",
|
|
5059
|
+
exportJsonKey: { responses: [{ response: "$", isCorrect: false }] },
|
|
3963
5060
|
description: "False values for a true/false statement/question",
|
|
3964
5061
|
maxCount: Count.infinity,
|
|
3965
5062
|
format: TagFormat.plainText
|
|
@@ -3969,6 +5066,7 @@ var GROUPS = {
|
|
|
3969
5066
|
description: "Item, lead, page number, margin number, instruction and hint tags"
|
|
3970
5067
|
},
|
|
3971
5068
|
{
|
|
5069
|
+
// @card scope-shift writes response-container isExample.
|
|
3972
5070
|
key: ConfigKey.property_example,
|
|
3973
5071
|
jsonKey: "example",
|
|
3974
5072
|
exportJsonKey: [
|
|
@@ -3976,20 +5074,333 @@ var GROUPS = {
|
|
|
3976
5074
|
"@keyonly": {
|
|
3977
5075
|
isExample: true,
|
|
3978
5076
|
example: "$parent.isCorrect",
|
|
3979
|
-
"@bit": { isExample: true }
|
|
5077
|
+
"@bit": { isExample: true },
|
|
5078
|
+
"@card": { isExample: true }
|
|
3980
5079
|
}
|
|
3981
5080
|
},
|
|
3982
5081
|
{
|
|
3983
5082
|
"@absent": {
|
|
3984
5083
|
isExample: true,
|
|
3985
5084
|
example: "$parent.isCorrect",
|
|
3986
|
-
"@bit": { isExample: true }
|
|
5085
|
+
"@bit": { isExample: true },
|
|
5086
|
+
"@card": { isExample: true }
|
|
3987
5087
|
}
|
|
3988
5088
|
},
|
|
3989
|
-
{
|
|
5089
|
+
{
|
|
5090
|
+
isExample: true,
|
|
5091
|
+
example: "$",
|
|
5092
|
+
"@bit": { isExample: true },
|
|
5093
|
+
"@card": { isExample: true }
|
|
5094
|
+
}
|
|
3990
5095
|
],
|
|
3991
5096
|
description: "An example for the true/false statement/question",
|
|
3992
|
-
format: TagFormat.
|
|
5097
|
+
format: TagFormat.boolean,
|
|
5098
|
+
nullable: true
|
|
5099
|
+
}
|
|
5100
|
+
]
|
|
5101
|
+
}
|
|
5102
|
+
]
|
|
5103
|
+
},
|
|
5104
|
+
[ConfigKey.group_trueFalseInlineSelect]: {
|
|
5105
|
+
type: GroupConfigType.standard,
|
|
5106
|
+
description: "True/False chain composed inline (select body-bit). +/- tags are consume-only at bit level.",
|
|
5107
|
+
tags: [
|
|
5108
|
+
{
|
|
5109
|
+
exportJsonKey: {
|
|
5110
|
+
"@body-inline": {
|
|
5111
|
+
type: "select",
|
|
5112
|
+
attrs: { options: [{ text: "$", isCorrect: true }] }
|
|
5113
|
+
}
|
|
5114
|
+
},
|
|
5115
|
+
key: ConfigKey.tag_true,
|
|
5116
|
+
description: "True value for an inline select body bit",
|
|
5117
|
+
maxCount: Count.infinity,
|
|
5118
|
+
format: TagFormat.plainText,
|
|
5119
|
+
chain: [
|
|
5120
|
+
{
|
|
5121
|
+
key: ConfigKey.tag_true,
|
|
5122
|
+
exportJsonKey: {},
|
|
5123
|
+
description: "True values for an inline select body bit",
|
|
5124
|
+
maxCount: Count.infinity,
|
|
5125
|
+
format: TagFormat.plainText
|
|
5126
|
+
},
|
|
5127
|
+
{
|
|
5128
|
+
key: ConfigKey.tag_false,
|
|
5129
|
+
exportJsonKey: {},
|
|
5130
|
+
description: "False values for an inline select body bit",
|
|
5131
|
+
maxCount: Count.infinity,
|
|
5132
|
+
format: TagFormat.plainText
|
|
5133
|
+
},
|
|
5134
|
+
{
|
|
5135
|
+
key: ConfigKey.group_standardItemLeadInstructionHint,
|
|
5136
|
+
description: "Item, lead, page number, margin number, instruction and hint tags"
|
|
5137
|
+
},
|
|
5138
|
+
{
|
|
5139
|
+
// PLAN-083: cascade-fired `@example` from bit header fires
|
|
5140
|
+
// `isExample:true, example:true` on the FIRST CORRECT option
|
|
5141
|
+
// only (BPG `fillBooleanExample(select.options,
|
|
5142
|
+
// firstCorrectOnly=true)`). Mirrors group_trueFalseInlineHighlight.
|
|
5143
|
+
// Every rule also bubbles `@bit:{isExample:true}` (idempotent
|
|
5144
|
+
// OR — any cascade ⇒ bit marked) and
|
|
5145
|
+
// `@body-inline:{attrs:{isExample:true}}` (node-level marker
|
|
5146
|
+
// on the select node).
|
|
5147
|
+
key: ConfigKey.property_example,
|
|
5148
|
+
jsonKey: "example",
|
|
5149
|
+
exportJsonKey: [
|
|
5150
|
+
{
|
|
5151
|
+
"@keyonly": {
|
|
5152
|
+
isExample: true,
|
|
5153
|
+
example: "$parent.isCorrect",
|
|
5154
|
+
"@bit": { isExample: true },
|
|
5155
|
+
"@body-inline": { attrs: { isExample: true } }
|
|
5156
|
+
}
|
|
5157
|
+
},
|
|
5158
|
+
{
|
|
5159
|
+
predicates: ["@absent", { "@parent.isCorrect": true }],
|
|
5160
|
+
maxEmits: 1,
|
|
5161
|
+
rule: {
|
|
5162
|
+
isExample: true,
|
|
5163
|
+
example: true,
|
|
5164
|
+
"@bit": { isExample: true },
|
|
5165
|
+
"@body-inline": { attrs: { isExample: true } }
|
|
5166
|
+
}
|
|
5167
|
+
},
|
|
5168
|
+
{
|
|
5169
|
+
isExample: true,
|
|
5170
|
+
example: "$",
|
|
5171
|
+
"@bit": { isExample: true },
|
|
5172
|
+
"@body-inline": { attrs: { isExample: true } }
|
|
5173
|
+
}
|
|
5174
|
+
],
|
|
5175
|
+
description: "An example for the inline select option (first correct only)",
|
|
5176
|
+
format: TagFormat.boolean,
|
|
5177
|
+
nullable: true
|
|
5178
|
+
}
|
|
5179
|
+
]
|
|
5180
|
+
},
|
|
5181
|
+
{
|
|
5182
|
+
exportJsonKey: {
|
|
5183
|
+
"@body-inline": {
|
|
5184
|
+
type: "select",
|
|
5185
|
+
attrs: { options: [{ text: "$", isCorrect: false }] }
|
|
5186
|
+
}
|
|
5187
|
+
},
|
|
5188
|
+
key: ConfigKey.tag_false,
|
|
5189
|
+
description: "False value for an inline select body bit",
|
|
5190
|
+
maxCount: Count.infinity,
|
|
5191
|
+
format: TagFormat.plainText,
|
|
5192
|
+
chain: [
|
|
5193
|
+
{
|
|
5194
|
+
key: ConfigKey.tag_true,
|
|
5195
|
+
exportJsonKey: {},
|
|
5196
|
+
description: "True values for an inline select body bit",
|
|
5197
|
+
maxCount: Count.infinity,
|
|
5198
|
+
format: TagFormat.plainText
|
|
5199
|
+
},
|
|
5200
|
+
{
|
|
5201
|
+
key: ConfigKey.tag_false,
|
|
5202
|
+
exportJsonKey: {},
|
|
5203
|
+
description: "False values for an inline select body bit",
|
|
5204
|
+
maxCount: Count.infinity,
|
|
5205
|
+
format: TagFormat.plainText
|
|
5206
|
+
},
|
|
5207
|
+
{
|
|
5208
|
+
key: ConfigKey.group_standardItemLeadInstructionHint,
|
|
5209
|
+
description: "Item, lead, page number, margin number, instruction and hint tags"
|
|
5210
|
+
},
|
|
5211
|
+
{
|
|
5212
|
+
// PLAN-083: identical to the `+` lead's chain @example —
|
|
5213
|
+
// cascade reaches the first correct option regardless of
|
|
5214
|
+
// which lead (+/-) opens the chain. Every firing rule bubbles
|
|
5215
|
+
// `@bit:{isExample:true}` and `@body-inline:{attrs:{isExample:true}}`.
|
|
5216
|
+
key: ConfigKey.property_example,
|
|
5217
|
+
jsonKey: "example",
|
|
5218
|
+
exportJsonKey: [
|
|
5219
|
+
{
|
|
5220
|
+
"@keyonly": {
|
|
5221
|
+
isExample: true,
|
|
5222
|
+
example: "$parent.isCorrect",
|
|
5223
|
+
"@bit": { isExample: true },
|
|
5224
|
+
"@body-inline": { attrs: { isExample: true } }
|
|
5225
|
+
}
|
|
5226
|
+
},
|
|
5227
|
+
{
|
|
5228
|
+
predicates: ["@absent", { "@parent.isCorrect": true }],
|
|
5229
|
+
maxEmits: 1,
|
|
5230
|
+
rule: {
|
|
5231
|
+
isExample: true,
|
|
5232
|
+
example: true,
|
|
5233
|
+
"@bit": { isExample: true },
|
|
5234
|
+
"@body-inline": { attrs: { isExample: true } }
|
|
5235
|
+
}
|
|
5236
|
+
},
|
|
5237
|
+
{
|
|
5238
|
+
isExample: true,
|
|
5239
|
+
example: "$",
|
|
5240
|
+
"@bit": { isExample: true },
|
|
5241
|
+
"@body-inline": { attrs: { isExample: true } }
|
|
5242
|
+
}
|
|
5243
|
+
],
|
|
5244
|
+
description: "An example for the inline select option (first correct only)",
|
|
5245
|
+
format: TagFormat.boolean,
|
|
5246
|
+
nullable: true
|
|
5247
|
+
}
|
|
5248
|
+
]
|
|
5249
|
+
}
|
|
5250
|
+
]
|
|
5251
|
+
},
|
|
5252
|
+
[ConfigKey.group_trueFalseInlineHighlight]: {
|
|
5253
|
+
type: GroupConfigType.standard,
|
|
5254
|
+
description: 'True/False chain composed inline (highlight body-bit). +/- tags emit `{type:"highlight", attrs:{texts:[{text,isCorrect?}]}}` inline body nodes. Used by highlight-text bit.',
|
|
5255
|
+
tags: [
|
|
5256
|
+
{
|
|
5257
|
+
exportJsonKey: {
|
|
5258
|
+
"@body-inline": {
|
|
5259
|
+
type: "highlight",
|
|
5260
|
+
attrs: { texts: [{ text: "$", isCorrect: true }] }
|
|
5261
|
+
}
|
|
5262
|
+
},
|
|
5263
|
+
key: ConfigKey.tag_true,
|
|
5264
|
+
description: "Highlighted span in the body (correct)",
|
|
5265
|
+
maxCount: Count.infinity,
|
|
5266
|
+
format: TagFormat.plainText,
|
|
5267
|
+
chain: [
|
|
5268
|
+
{
|
|
5269
|
+
key: ConfigKey.tag_true,
|
|
5270
|
+
exportJsonKey: {},
|
|
5271
|
+
description: "Highlighted span values (correct)",
|
|
5272
|
+
maxCount: Count.infinity,
|
|
5273
|
+
format: TagFormat.plainText
|
|
5274
|
+
},
|
|
5275
|
+
{
|
|
5276
|
+
key: ConfigKey.tag_false,
|
|
5277
|
+
exportJsonKey: {},
|
|
5278
|
+
description: "Highlighted span values (incorrect)",
|
|
5279
|
+
maxCount: Count.infinity,
|
|
5280
|
+
format: TagFormat.plainText
|
|
5281
|
+
},
|
|
5282
|
+
{
|
|
5283
|
+
key: ConfigKey.group_standardItemLeadInstructionHint,
|
|
5284
|
+
description: "Item, lead, page number, margin number, instruction and hint tags"
|
|
5285
|
+
},
|
|
5286
|
+
{
|
|
5287
|
+
// PLAN-072 + ALIGN-EXAMPLE-CASCADE §3.13: cascade-fired
|
|
5288
|
+
// `@example` from the bit header fires `example: true` +
|
|
5289
|
+
// `isExample: true` on the FIRST correct highlight only
|
|
5290
|
+
// (BPG `fillBooleanExample(highlight.texts,
|
|
5291
|
+
// firstCorrectOnly=true)`). Incorrect highlights get
|
|
5292
|
+
// nothing (unlike choices, the highlight-text fixture
|
|
5293
|
+
// shows no `isExample` on incorrect entries).
|
|
5294
|
+
key: ConfigKey.property_example,
|
|
5295
|
+
jsonKey: "example",
|
|
5296
|
+
exportJsonKey: [
|
|
5297
|
+
{
|
|
5298
|
+
"@keyonly": {
|
|
5299
|
+
isExample: true,
|
|
5300
|
+
example: "$parent.isCorrect",
|
|
5301
|
+
"@bit": { isExample: true },
|
|
5302
|
+
"@body-inline": { attrs: { isExample: true } }
|
|
5303
|
+
}
|
|
5304
|
+
},
|
|
5305
|
+
{
|
|
5306
|
+
predicates: ["@absent", { "@parent.isCorrect": true }],
|
|
5307
|
+
maxEmits: 1,
|
|
5308
|
+
rule: {
|
|
5309
|
+
isExample: true,
|
|
5310
|
+
example: true,
|
|
5311
|
+
"@bit": { isExample: true },
|
|
5312
|
+
"@body-inline": { attrs: { isExample: true } }
|
|
5313
|
+
}
|
|
5314
|
+
},
|
|
5315
|
+
{
|
|
5316
|
+
isExample: true,
|
|
5317
|
+
example: "$",
|
|
5318
|
+
"@bit": { isExample: true },
|
|
5319
|
+
"@body-inline": { attrs: { isExample: true } }
|
|
5320
|
+
}
|
|
5321
|
+
],
|
|
5322
|
+
description: "An example for the highlighted span (first correct only)",
|
|
5323
|
+
format: TagFormat.boolean,
|
|
5324
|
+
nullable: true
|
|
5325
|
+
}
|
|
5326
|
+
]
|
|
5327
|
+
},
|
|
5328
|
+
{
|
|
5329
|
+
// PLAN-083 follow-up (2026-05-22): mirror the select `-` lead shape
|
|
5330
|
+
// by writing `isCorrect: false`. Without it, `$parent.isCorrect` in
|
|
5331
|
+
// chain-child @example rules resolves to ParentPathMissing for `-`
|
|
5332
|
+
// entries (e.g. `[-fish][@example]`), so the @keyonly rule falls
|
|
5333
|
+
// through and nothing fires. The natural-default strip removes
|
|
5334
|
+
// `isCorrect: false` from the final texts entries (matches fixture).
|
|
5335
|
+
exportJsonKey: {
|
|
5336
|
+
"@body-inline": {
|
|
5337
|
+
type: "highlight",
|
|
5338
|
+
attrs: { texts: [{ text: "$", isCorrect: false }] }
|
|
5339
|
+
}
|
|
5340
|
+
},
|
|
5341
|
+
key: ConfigKey.tag_false,
|
|
5342
|
+
description: "Highlighted span in the body (incorrect)",
|
|
5343
|
+
maxCount: Count.infinity,
|
|
5344
|
+
format: TagFormat.plainText,
|
|
5345
|
+
chain: [
|
|
5346
|
+
{
|
|
5347
|
+
key: ConfigKey.tag_true,
|
|
5348
|
+
exportJsonKey: {},
|
|
5349
|
+
description: "Highlighted span values (correct)",
|
|
5350
|
+
maxCount: Count.infinity,
|
|
5351
|
+
format: TagFormat.plainText
|
|
5352
|
+
},
|
|
5353
|
+
{
|
|
5354
|
+
key: ConfigKey.tag_false,
|
|
5355
|
+
exportJsonKey: {},
|
|
5356
|
+
description: "Highlighted span values (incorrect)",
|
|
5357
|
+
maxCount: Count.infinity,
|
|
5358
|
+
format: TagFormat.plainText
|
|
5359
|
+
},
|
|
5360
|
+
{
|
|
5361
|
+
key: ConfigKey.group_standardItemLeadInstructionHint,
|
|
5362
|
+
description: "Item, lead, page number, margin number, instruction and hint tags"
|
|
5363
|
+
},
|
|
5364
|
+
{
|
|
5365
|
+
// PLAN-072 + ALIGN-EXAMPLE-CASCADE §3.13: cascade-fired
|
|
5366
|
+
// `@example` on a `-` (incorrect highlight) emits nothing
|
|
5367
|
+
// (highlight-text fixture has no `isExample` on incorrect
|
|
5368
|
+
// entries; firstCorrectOnly cascade skips them entirely).
|
|
5369
|
+
// No `@absent` rule.
|
|
5370
|
+
// PLAN-083: identical to the `+` lead's chain @example —
|
|
5371
|
+
// cascade reaches the first correct option regardless of
|
|
5372
|
+
// which lead (+/-) opens the chain.
|
|
5373
|
+
key: ConfigKey.property_example,
|
|
5374
|
+
jsonKey: "example",
|
|
5375
|
+
exportJsonKey: [
|
|
5376
|
+
{
|
|
5377
|
+
"@keyonly": {
|
|
5378
|
+
isExample: true,
|
|
5379
|
+
example: "$parent.isCorrect",
|
|
5380
|
+
"@bit": { isExample: true },
|
|
5381
|
+
"@body-inline": { attrs: { isExample: true } }
|
|
5382
|
+
}
|
|
5383
|
+
},
|
|
5384
|
+
{
|
|
5385
|
+
predicates: ["@absent", { "@parent.isCorrect": true }],
|
|
5386
|
+
maxEmits: 1,
|
|
5387
|
+
rule: {
|
|
5388
|
+
isExample: true,
|
|
5389
|
+
example: true,
|
|
5390
|
+
"@bit": { isExample: true },
|
|
5391
|
+
"@body-inline": { attrs: { isExample: true } }
|
|
5392
|
+
}
|
|
5393
|
+
},
|
|
5394
|
+
{
|
|
5395
|
+
isExample: true,
|
|
5396
|
+
example: "$",
|
|
5397
|
+
"@bit": { isExample: true },
|
|
5398
|
+
"@body-inline": { attrs: { isExample: true } }
|
|
5399
|
+
}
|
|
5400
|
+
],
|
|
5401
|
+
description: "An example for the highlighted span (first correct only)",
|
|
5402
|
+
format: TagFormat.boolean,
|
|
5403
|
+
nullable: true
|
|
3993
5404
|
}
|
|
3994
5405
|
]
|
|
3995
5406
|
}
|
|
@@ -4002,7 +5413,11 @@ var GROUPS = {
|
|
|
4002
5413
|
{
|
|
4003
5414
|
key: ConfigKey.property_mark,
|
|
4004
5415
|
jsonKey: "marks.mark",
|
|
4005
|
-
|
|
5416
|
+
// `emphasis: 'underline'` is the per-entry default for mark
|
|
5417
|
+
// configs — mirrors the hardcoded default in BPG
|
|
5418
|
+
// `MarkConfigChainContentProcessor.ts:55-58`. Chained
|
|
5419
|
+
// `[@emphasis:…]` overrides via deep-merge into `marks[-1]`.
|
|
5420
|
+
exportJsonKey: { marks: [{ mark: "$", emphasis: "underline" }] },
|
|
4006
5421
|
description: "The mark configuration",
|
|
4007
5422
|
format: TagFormat.plainText,
|
|
4008
5423
|
maxCount: Count.infinity,
|
|
@@ -4026,7 +5441,9 @@ var GROUPS = {
|
|
|
4026
5441
|
description: "Mark chain",
|
|
4027
5442
|
tags: [
|
|
4028
5443
|
{
|
|
4029
|
-
exportJsonKey: {
|
|
5444
|
+
exportJsonKey: {
|
|
5445
|
+
"@body-inline": { type: "mark", attrs: { solution: "$" } }
|
|
5446
|
+
},
|
|
4030
5447
|
key: ConfigKey.tag_mark,
|
|
4031
5448
|
description: "The marked content",
|
|
4032
5449
|
maxCount: Count.infinity,
|
|
@@ -4035,19 +5452,30 @@ var GROUPS = {
|
|
|
4035
5452
|
{
|
|
4036
5453
|
key: ConfigKey.property_mark,
|
|
4037
5454
|
jsonKey: "mark",
|
|
5455
|
+
exportJsonKey: { mark: "$" },
|
|
4038
5456
|
description: "The mark configuration",
|
|
4039
5457
|
format: TagFormat.plainText
|
|
4040
5458
|
},
|
|
4041
5459
|
{
|
|
4042
5460
|
key: ConfigKey.property_example,
|
|
4043
5461
|
jsonKey: "example",
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
5462
|
+
// Per-mark `@example` is a boolean toggle. BPG's runtime
|
|
5463
|
+
// `ExampleTagContentProcessor.handleDefaultOnlyExample`
|
|
5464
|
+
// (lines 147-161) normalises ANY `[@example]` or
|
|
5465
|
+
// `[@example:V]` on a mark body-bit to
|
|
5466
|
+
// `__isDefaultExample=true, example=undefined`; the
|
|
5467
|
+
// downstream `toExample(_, _, defaultExample=true)` in
|
|
5468
|
+
// `BaseBuilder.ts:80-87` then emits `example: true`
|
|
5469
|
+
// regardless of source. Declare a single unconditional
|
|
5470
|
+
// boolean-shape rule so the exported schema matches that
|
|
5471
|
+
// runtime semantic instead of the misleading `example: $`
|
|
5472
|
+
// shape (which would only fire if `handleDefaultOnlyExample`
|
|
5473
|
+
// were removed). `@bit: {isExample: true}` cascades the
|
|
5474
|
+
// marker — but NOT the `example` value — to bit root.
|
|
5475
|
+
exportJsonKey: { isExample: true, example: true, "@bit": { isExample: true } },
|
|
4049
5476
|
description: "An example for the marked content",
|
|
4050
|
-
format: TagFormat.
|
|
5477
|
+
format: TagFormat.boolean,
|
|
5478
|
+
nullable: true
|
|
4051
5479
|
}
|
|
4052
5480
|
]
|
|
4053
5481
|
}
|
|
@@ -4476,6 +5904,42 @@ var GROUPS = {
|
|
|
4476
5904
|
}
|
|
4477
5905
|
]
|
|
4478
5906
|
},
|
|
5907
|
+
[ConfigKey.group_backgroundWallpaper]: {
|
|
5908
|
+
type: GroupConfigType.standard,
|
|
5909
|
+
description: "Background wallpaper tags",
|
|
5910
|
+
tags: [
|
|
5911
|
+
{
|
|
5912
|
+
key: ConfigKey.resource_backgroundWallpaper,
|
|
5913
|
+
exportJsonKey: { backgroundWallpaper: { type: "image", image: { src: "$" } } },
|
|
5914
|
+
description: "Background wallpaper for the image, used to set a background for the image",
|
|
5915
|
+
chain: [
|
|
5916
|
+
{
|
|
5917
|
+
key: ConfigKey.group_resourceImageCommon,
|
|
5918
|
+
description: "Common resource image tags for images"
|
|
5919
|
+
}
|
|
5920
|
+
]
|
|
5921
|
+
}
|
|
5922
|
+
]
|
|
5923
|
+
},
|
|
5924
|
+
[ConfigKey.group_imageNoZoom]: {
|
|
5925
|
+
type: GroupConfigType.standard,
|
|
5926
|
+
description: "Image bit tags",
|
|
5927
|
+
tags: [
|
|
5928
|
+
{
|
|
5929
|
+
key: ConfigKey.group_backgroundWallpaper,
|
|
5930
|
+
description: "Background wallpaper tags for images, used to define background properties for images"
|
|
5931
|
+
},
|
|
5932
|
+
{
|
|
5933
|
+
key: ConfigKey.group_resourceBitTags,
|
|
5934
|
+
description: "Resource bit tags for images, used to define additional properties for images"
|
|
5935
|
+
},
|
|
5936
|
+
{
|
|
5937
|
+
key: ConfigKey.group_resourceImageNoZoom,
|
|
5938
|
+
description: "Resource image tags for images, used to attach images to the bit",
|
|
5939
|
+
minCount: 1
|
|
5940
|
+
}
|
|
5941
|
+
]
|
|
5942
|
+
},
|
|
4479
5943
|
//
|
|
4480
5944
|
// Resource groups
|
|
4481
5945
|
//
|
|
@@ -4595,6 +6059,58 @@ var GROUPS = {
|
|
|
4595
6059
|
}
|
|
4596
6060
|
]
|
|
4597
6061
|
},
|
|
6062
|
+
[ConfigKey.group_resourceImageCommonNoZoom]: {
|
|
6063
|
+
type: GroupConfigType.standard,
|
|
6064
|
+
description: "Common properties for image resources where @zoomDisabled defaults to true",
|
|
6065
|
+
tags: [
|
|
6066
|
+
{
|
|
6067
|
+
key: ConfigKey.group_resourceCommon,
|
|
6068
|
+
description: "Common resource properties"
|
|
6069
|
+
},
|
|
6070
|
+
{
|
|
6071
|
+
key: ConfigKey.property_src1x,
|
|
6072
|
+
description: "The source URL for the image at 1x resolution",
|
|
6073
|
+
format: TagFormat.plainText
|
|
6074
|
+
},
|
|
6075
|
+
{
|
|
6076
|
+
key: ConfigKey.property_src2x,
|
|
6077
|
+
description: "The source URL for the image at 2x resolution",
|
|
6078
|
+
format: TagFormat.plainText
|
|
6079
|
+
},
|
|
6080
|
+
{
|
|
6081
|
+
key: ConfigKey.property_src3x,
|
|
6082
|
+
description: "The source URL for the image at 3x resolution",
|
|
6083
|
+
format: TagFormat.plainText
|
|
6084
|
+
},
|
|
6085
|
+
{
|
|
6086
|
+
key: ConfigKey.property_src4x,
|
|
6087
|
+
description: "The source URL for the image at 4x resolution",
|
|
6088
|
+
format: TagFormat.plainText
|
|
6089
|
+
},
|
|
6090
|
+
{
|
|
6091
|
+
key: ConfigKey.property_width,
|
|
6092
|
+
description: "The width of the image",
|
|
6093
|
+
format: TagFormat.number
|
|
6094
|
+
},
|
|
6095
|
+
{
|
|
6096
|
+
key: ConfigKey.property_height,
|
|
6097
|
+
description: "The height of the image",
|
|
6098
|
+
format: TagFormat.number
|
|
6099
|
+
},
|
|
6100
|
+
{
|
|
6101
|
+
key: ConfigKey.property_alt,
|
|
6102
|
+
description: "The alternative text for the image",
|
|
6103
|
+
format: TagFormat.plainText
|
|
6104
|
+
},
|
|
6105
|
+
// @zoomDisabled with a true default
|
|
6106
|
+
{
|
|
6107
|
+
key: ConfigKey.property_zoomDisabled,
|
|
6108
|
+
description: "If true, zooming is disabled for the image (defaults to true)",
|
|
6109
|
+
format: TagFormat.boolean,
|
|
6110
|
+
defaultValue: "true"
|
|
6111
|
+
}
|
|
6112
|
+
]
|
|
6113
|
+
},
|
|
4598
6114
|
[ConfigKey.group_resourceAudioCommon]: {
|
|
4599
6115
|
type: GroupConfigType.standard,
|
|
4600
6116
|
description: "Common properties for audio resources",
|
|
@@ -4684,22 +6200,26 @@ var GROUPS = {
|
|
|
4684
6200
|
{
|
|
4685
6201
|
key: ConfigKey.property_src1x,
|
|
4686
6202
|
description: "The source URL for the video at 1x resolution",
|
|
4687
|
-
format: TagFormat.plainText
|
|
6203
|
+
format: TagFormat.plainText,
|
|
6204
|
+
exportJsonKey: { thumbnails: [{ src: "$" }] }
|
|
4688
6205
|
},
|
|
4689
6206
|
{
|
|
4690
6207
|
key: ConfigKey.property_src2x,
|
|
4691
6208
|
description: "The source URL for the video at 2x resolution",
|
|
4692
|
-
format: TagFormat.plainText
|
|
6209
|
+
format: TagFormat.plainText,
|
|
6210
|
+
exportJsonKey: { thumbnails: [{ src: "$" }] }
|
|
4693
6211
|
},
|
|
4694
6212
|
{
|
|
4695
6213
|
key: ConfigKey.property_src3x,
|
|
4696
6214
|
description: "The source URL for the video at 3x resolution",
|
|
4697
|
-
format: TagFormat.plainText
|
|
6215
|
+
format: TagFormat.plainText,
|
|
6216
|
+
exportJsonKey: { thumbnails: [{ src: "$" }] }
|
|
4698
6217
|
},
|
|
4699
6218
|
{
|
|
4700
6219
|
key: ConfigKey.property_src4x,
|
|
4701
6220
|
description: "The source URL for the video at 4x resolution",
|
|
4702
|
-
format: TagFormat.plainText
|
|
6221
|
+
format: TagFormat.plainText,
|
|
6222
|
+
exportJsonKey: { thumbnails: [{ src: "$" }] }
|
|
4703
6223
|
}
|
|
4704
6224
|
]
|
|
4705
6225
|
},
|
|
@@ -4712,6 +6232,7 @@ var GROUPS = {
|
|
|
4712
6232
|
tags: [
|
|
4713
6233
|
{
|
|
4714
6234
|
key: ConfigKey.resource_icon,
|
|
6235
|
+
exportJsonKey: { resource: { type: "icon", icon: { src: "$" } } },
|
|
4715
6236
|
description: "The icon resource",
|
|
4716
6237
|
chain: [
|
|
4717
6238
|
{
|
|
@@ -4739,12 +6260,30 @@ var GROUPS = {
|
|
|
4739
6260
|
}
|
|
4740
6261
|
]
|
|
4741
6262
|
},
|
|
6263
|
+
[ConfigKey.group_resourceImageNoZoom]: {
|
|
6264
|
+
type: GroupConfigType.resource,
|
|
6265
|
+
description: "Image resource where @zoomDisabled defaults to true",
|
|
6266
|
+
tags: [
|
|
6267
|
+
{
|
|
6268
|
+
key: ConfigKey.resource_image,
|
|
6269
|
+
exportJsonKey: { resource: { type: "image", image: { src: "$" } } },
|
|
6270
|
+
description: "The image resource (no-zoom default)",
|
|
6271
|
+
chain: [
|
|
6272
|
+
{
|
|
6273
|
+
key: ConfigKey.group_resourceImageCommonNoZoom,
|
|
6274
|
+
description: "Common image properties with @zoomDisabled defaulting to true"
|
|
6275
|
+
}
|
|
6276
|
+
]
|
|
6277
|
+
}
|
|
6278
|
+
]
|
|
6279
|
+
},
|
|
4742
6280
|
[ConfigKey.group_resourceImagePortrait]: {
|
|
4743
6281
|
type: GroupConfigType.resource,
|
|
4744
6282
|
description: "Portrait image resource",
|
|
4745
6283
|
tags: [
|
|
4746
6284
|
{
|
|
4747
6285
|
key: ConfigKey.resource_imagePortrait,
|
|
6286
|
+
exportJsonKey: { resource: { type: "image-portrait", imagePortrait: { src: "$" } } },
|
|
4748
6287
|
description: "The portrait image resource",
|
|
4749
6288
|
chain: [
|
|
4750
6289
|
{
|
|
@@ -4761,6 +6300,7 @@ var GROUPS = {
|
|
|
4761
6300
|
tags: [
|
|
4762
6301
|
{
|
|
4763
6302
|
key: ConfigKey.resource_imageLandscape,
|
|
6303
|
+
exportJsonKey: { resource: { type: "image-landscape", imageLandscape: { src: "$" } } },
|
|
4764
6304
|
description: "The landscape image resource",
|
|
4765
6305
|
chain: [
|
|
4766
6306
|
{
|
|
@@ -4777,6 +6317,7 @@ var GROUPS = {
|
|
|
4777
6317
|
tags: [
|
|
4778
6318
|
{
|
|
4779
6319
|
key: ConfigKey.resource_imageEmbed,
|
|
6320
|
+
exportJsonKey: { resource: { type: "image-embed", imageEmbed: { src: "$" } } },
|
|
4780
6321
|
description: "The embedded image resource",
|
|
4781
6322
|
chain: [
|
|
4782
6323
|
{
|
|
@@ -4950,6 +6491,7 @@ var GROUPS = {
|
|
|
4950
6491
|
tags: [
|
|
4951
6492
|
{
|
|
4952
6493
|
key: ConfigKey.resource_article,
|
|
6494
|
+
exportJsonKey: { resource: { type: "article", article: { body: "$" } } },
|
|
4953
6495
|
description: "The article resource",
|
|
4954
6496
|
chain: [
|
|
4955
6497
|
{
|
|
@@ -4966,6 +6508,7 @@ var GROUPS = {
|
|
|
4966
6508
|
tags: [
|
|
4967
6509
|
{
|
|
4968
6510
|
key: ConfigKey.resource_articleEmbed,
|
|
6511
|
+
exportJsonKey: { resource: { type: "article-embed", articleEmbed: { url: "$" } } },
|
|
4969
6512
|
description: "The embedded article resource",
|
|
4970
6513
|
chain: [
|
|
4971
6514
|
{
|
|
@@ -4982,6 +6525,7 @@ var GROUPS = {
|
|
|
4982
6525
|
tags: [
|
|
4983
6526
|
{
|
|
4984
6527
|
key: ConfigKey.resource_articleLink,
|
|
6528
|
+
exportJsonKey: { resource: { type: "article-link", articleLink: { url: "$" } } },
|
|
4985
6529
|
description: "The link to the article resource",
|
|
4986
6530
|
chain: [
|
|
4987
6531
|
{
|
|
@@ -5252,7 +6796,12 @@ var ConfigHydrator = class {
|
|
|
5252
6796
|
isDefault: v.isDefault,
|
|
5253
6797
|
sideJsonKey: v.sideJsonKey,
|
|
5254
6798
|
sideExportJsonKey: v.sideExportJsonKey,
|
|
5255
|
-
hasSideExportJsonKey: Object.prototype.hasOwnProperty.call(v, "sideExportJsonKey")
|
|
6799
|
+
hasSideExportJsonKey: Object.prototype.hasOwnProperty.call(v, "sideExportJsonKey"),
|
|
6800
|
+
// PLAN-085: cardinality fields pass through; defaults applied
|
|
6801
|
+
// downstream by the config consumer (Rust treats `0` / undefined
|
|
6802
|
+
// as unbounded).
|
|
6803
|
+
minCount: v.minCount,
|
|
6804
|
+
maxCount: v.maxCount
|
|
5256
6805
|
};
|
|
5257
6806
|
}
|
|
5258
6807
|
return out;
|
|
@@ -5467,7 +7016,8 @@ var BITS = {
|
|
|
5467
7016
|
{ isExample: true, example: "$" }
|
|
5468
7017
|
],
|
|
5469
7018
|
description: "The example text for the bit",
|
|
5470
|
-
format: TagFormat.plainText
|
|
7019
|
+
format: TagFormat.plainText,
|
|
7020
|
+
nullable: true
|
|
5471
7021
|
}
|
|
5472
7022
|
],
|
|
5473
7023
|
rootExampleType: ExampleType.string
|
|
@@ -5806,7 +7356,8 @@ var BITS = {
|
|
|
5806
7356
|
{ isExample: true, example: "$" }
|
|
5807
7357
|
],
|
|
5808
7358
|
description: "The example text for the bit",
|
|
5809
|
-
format: TagFormat.plainText
|
|
7359
|
+
format: TagFormat.plainText,
|
|
7360
|
+
nullable: true
|
|
5810
7361
|
}
|
|
5811
7362
|
],
|
|
5812
7363
|
rootExampleType: ExampleType.string
|
|
@@ -5969,7 +7520,8 @@ var BITS = {
|
|
|
5969
7520
|
{ isExample: true, example: "$" }
|
|
5970
7521
|
],
|
|
5971
7522
|
description: "The example text for the bit",
|
|
5972
|
-
format: TagFormat.plainText
|
|
7523
|
+
format: TagFormat.plainText,
|
|
7524
|
+
nullable: true
|
|
5973
7525
|
}
|
|
5974
7526
|
],
|
|
5975
7527
|
rootExampleType: ExampleType.string
|
|
@@ -6120,7 +7672,8 @@ var BITS = {
|
|
|
6120
7672
|
{ isExample: true, example: "$" }
|
|
6121
7673
|
],
|
|
6122
7674
|
description: "The example text for the bit",
|
|
6123
|
-
format: TagFormat.plainText
|
|
7675
|
+
format: TagFormat.plainText,
|
|
7676
|
+
nullable: true
|
|
6124
7677
|
}
|
|
6125
7678
|
],
|
|
6126
7679
|
rootExampleType: ExampleType.string
|
|
@@ -6697,6 +8250,12 @@ var BITS = {
|
|
|
6697
8250
|
description: "The progress of the chapter, used for tracking reading progress",
|
|
6698
8251
|
format: TagFormat.boolean,
|
|
6699
8252
|
defaultValue: "true"
|
|
8253
|
+
},
|
|
8254
|
+
{
|
|
8255
|
+
key: ConfigKey.property_isCollapsible,
|
|
8256
|
+
description: "If true, the chapter is collapsible",
|
|
8257
|
+
format: TagFormat.boolean,
|
|
8258
|
+
defaultValue: "false"
|
|
6700
8259
|
}
|
|
6701
8260
|
]
|
|
6702
8261
|
},
|
|
@@ -6715,8 +8274,31 @@ var BITS = {
|
|
|
6715
8274
|
description: "Tags for gaps in cloze and multiple choice text bits"
|
|
6716
8275
|
},
|
|
6717
8276
|
{
|
|
6718
|
-
key: ConfigKey.
|
|
8277
|
+
key: ConfigKey.group_trueFalseInlineSelect,
|
|
6719
8278
|
description: "Tags for true/false questions in cloze and multiple choice text bits"
|
|
8279
|
+
},
|
|
8280
|
+
{
|
|
8281
|
+
// Bit-level @isCaseSensitive — empty exportJsonKey (no bit-level emission);
|
|
8282
|
+
// defaultValue 'true' phantom-fires onto the ancestor stack so the gap chain's
|
|
8283
|
+
// `@absent: $ancestor` rule resolves to true. Mirrors BPG Builder.ts:1643-1652
|
|
8284
|
+
// `pushDownTree(... 'isCaseSensitive', data.isCaseSensitive ?? true)` for cloze bits.
|
|
8285
|
+
key: ConfigKey.property_isCaseSensitive,
|
|
8286
|
+
exportJsonKey: {},
|
|
8287
|
+
description: "If the cloze answers are case sensitive",
|
|
8288
|
+
format: TagFormat.boolean,
|
|
8289
|
+
defaultValue: "true"
|
|
8290
|
+
},
|
|
8291
|
+
{
|
|
8292
|
+
// Override inherited @example: in `isTopLevelExample` allow-list.
|
|
8293
|
+
key: ConfigKey.property_example,
|
|
8294
|
+
exportJsonKey: [
|
|
8295
|
+
{ "@keyonly": { isExample: true } },
|
|
8296
|
+
{ "@absent": { isExample: true } },
|
|
8297
|
+
{ isExample: true, example: "$" }
|
|
8298
|
+
],
|
|
8299
|
+
description: "The example(s) for the bit",
|
|
8300
|
+
format: TagFormat.bitmarkText,
|
|
8301
|
+
nullable: true
|
|
6720
8302
|
}
|
|
6721
8303
|
]
|
|
6722
8304
|
},
|
|
@@ -6755,18 +8337,56 @@ var BITS = {
|
|
|
6755
8337
|
{
|
|
6756
8338
|
key: ConfigKey.group_gap,
|
|
6757
8339
|
description: "Tags for gaps in cloze bits"
|
|
8340
|
+
},
|
|
8341
|
+
{
|
|
8342
|
+
// Override inherited @example: cloze is in OLD parser's
|
|
8343
|
+
// `isTopLevelExample` allow-list, so [@example] at bit-header
|
|
8344
|
+
// emits `isExample: true`.
|
|
8345
|
+
key: ConfigKey.property_example,
|
|
8346
|
+
exportJsonKey: [
|
|
8347
|
+
{ "@keyonly": { isExample: true } },
|
|
8348
|
+
{ "@absent": { isExample: true } },
|
|
8349
|
+
{ isExample: true, example: "$" }
|
|
8350
|
+
],
|
|
8351
|
+
description: "The example(s) for the bit",
|
|
8352
|
+
format: TagFormat.bitmarkText,
|
|
8353
|
+
nullable: true
|
|
6758
8354
|
}
|
|
6759
8355
|
]
|
|
6760
8356
|
},
|
|
6761
8357
|
[BitType.clozeInstructionGrouped]: {
|
|
6762
8358
|
since: "1.3.0",
|
|
6763
8359
|
baseBitType: BitType.cloze,
|
|
6764
|
-
description: "Cloze instruction grouped bit, used for cloze questions with grouped instructions"
|
|
8360
|
+
description: "Cloze instruction grouped bit, used for cloze questions with grouped instructions",
|
|
8361
|
+
tags: [
|
|
8362
|
+
{
|
|
8363
|
+
key: ConfigKey.property_quizCountItems,
|
|
8364
|
+
description: "The number of items in the cloze quiz (instruction-grouped default: true)",
|
|
8365
|
+
format: TagFormat.boolean,
|
|
8366
|
+
defaultValue: "true"
|
|
8367
|
+
}
|
|
8368
|
+
// Note: quizStrikethroughSolutions defaults to false for instruction-grouped per OLD
|
|
8369
|
+
// JsonGenerator.ts:1689-1690, which equals the Boolean natural default → no override needed.
|
|
8370
|
+
]
|
|
6765
8371
|
},
|
|
6766
8372
|
[BitType.clozeSolutionGrouped]: {
|
|
6767
8373
|
since: "1.3.0",
|
|
6768
8374
|
baseBitType: BitType.cloze,
|
|
6769
|
-
description: "Cloze solution grouped bit, used for cloze questions with grouped solutions"
|
|
8375
|
+
description: "Cloze solution grouped bit, used for cloze questions with grouped solutions",
|
|
8376
|
+
tags: [
|
|
8377
|
+
{
|
|
8378
|
+
key: ConfigKey.property_quizCountItems,
|
|
8379
|
+
description: "The number of items in the cloze quiz (solution-grouped default: true)",
|
|
8380
|
+
format: TagFormat.boolean,
|
|
8381
|
+
defaultValue: "true"
|
|
8382
|
+
},
|
|
8383
|
+
{
|
|
8384
|
+
key: ConfigKey.property_quizStrikethroughSolutions,
|
|
8385
|
+
description: "If the cloze solutions should be strikethrough (solution-grouped default: true)",
|
|
8386
|
+
format: TagFormat.boolean,
|
|
8387
|
+
defaultValue: "true"
|
|
8388
|
+
}
|
|
8389
|
+
]
|
|
6770
8390
|
},
|
|
6771
8391
|
[BitType.clozeSeveral]: {
|
|
6772
8392
|
since: "3.5.0",
|
|
@@ -6802,6 +8422,17 @@ var BITS = {
|
|
|
6802
8422
|
{
|
|
6803
8423
|
key: ConfigKey.group_quizCommon,
|
|
6804
8424
|
description: "Common tags for quiz bits"
|
|
8425
|
+
},
|
|
8426
|
+
{
|
|
8427
|
+
// PLAN-084 (R12): bit-level @isCaseSensitive — empty exportJsonKey
|
|
8428
|
+
// (no bit-level emission); defaultValue 'true' phantom-fires onto
|
|
8429
|
+
// the ancestor stack so each listItem's gap chain `@absent: $ancestor`
|
|
8430
|
+
// rule resolves to true. Mirrors the cloze bit (bits.ts:1326-1336).
|
|
8431
|
+
key: ConfigKey.property_isCaseSensitive,
|
|
8432
|
+
exportJsonKey: {},
|
|
8433
|
+
description: "If the cloze answers are case sensitive",
|
|
8434
|
+
format: TagFormat.boolean,
|
|
8435
|
+
defaultValue: "true"
|
|
6805
8436
|
}
|
|
6806
8437
|
],
|
|
6807
8438
|
cardSet: CardSetConfigKey.clozeList
|
|
@@ -6968,6 +8599,7 @@ var BITS = {
|
|
|
6968
8599
|
tags: [
|
|
6969
8600
|
{
|
|
6970
8601
|
key: ConfigKey.group_person,
|
|
8602
|
+
exportJsonKey: { partner: { name: "$" } },
|
|
6971
8603
|
description: "Tags for the person in the conversation"
|
|
6972
8604
|
}
|
|
6973
8605
|
]
|
|
@@ -7398,7 +9030,12 @@ var BITS = {
|
|
|
7398
9030
|
maxCount: Count.infinity
|
|
7399
9031
|
},
|
|
7400
9032
|
{
|
|
7401
|
-
// Override inherited @example from group_standardTags:
|
|
9033
|
+
// Override inherited @example from group_standardTags: emits both
|
|
9034
|
+
// `isExample` (bool) AND `example` (paragraph array). Declared as
|
|
9035
|
+
// bitmarkText because BPG `JsonGenerator.toExample` renders the
|
|
9036
|
+
// root-example value through the bit's textFormat (not this tag's
|
|
9037
|
+
// declared format) — `TagFormat.plainText` here would be ignored
|
|
9038
|
+
// at runtime and is misleading.
|
|
7402
9039
|
key: ConfigKey.property_example,
|
|
7403
9040
|
exportJsonKey: [
|
|
7404
9041
|
{ "@keyonly": { isExample: true, example: "true" } },
|
|
@@ -7406,7 +9043,8 @@ var BITS = {
|
|
|
7406
9043
|
{ isExample: true, example: "$" }
|
|
7407
9044
|
],
|
|
7408
9045
|
description: "The example text for the essay bit",
|
|
7409
|
-
format: TagFormat.
|
|
9046
|
+
format: TagFormat.bitmarkText,
|
|
9047
|
+
nullable: true
|
|
7410
9048
|
}
|
|
7411
9049
|
],
|
|
7412
9050
|
rootExampleType: ExampleType.string
|
|
@@ -7440,7 +9078,8 @@ var BITS = {
|
|
|
7440
9078
|
{ isExample: true, example: "$" }
|
|
7441
9079
|
],
|
|
7442
9080
|
description: "The example text for the example bit",
|
|
7443
|
-
format: TagFormat.plainText
|
|
9081
|
+
format: TagFormat.plainText,
|
|
9082
|
+
nullable: true
|
|
7444
9083
|
}
|
|
7445
9084
|
],
|
|
7446
9085
|
rootExampleType: ExampleType.string
|
|
@@ -7526,7 +9165,19 @@ var BITS = {
|
|
|
7526
9165
|
[BitType.articleAi]: {
|
|
7527
9166
|
since: "1.3.0",
|
|
7528
9167
|
baseBitType: BitType.article,
|
|
7529
|
-
description: "Article AI bit, used to create AI-generated articles"
|
|
9168
|
+
description: "Article AI bit, used to create AI-generated articles",
|
|
9169
|
+
tags: [
|
|
9170
|
+
{
|
|
9171
|
+
// Shadows the inherited @aiGenerated from the standard group: this
|
|
9172
|
+
// bit type forces aiGenerated:true regardless of source value or
|
|
9173
|
+
// absence. Literal `true` in the export pattern ignores `$`.
|
|
9174
|
+
key: ConfigKey.property_aiGenerated,
|
|
9175
|
+
description: "AI-generated flag, forced true for article-ai bits",
|
|
9176
|
+
format: TagFormat.boolean,
|
|
9177
|
+
defaultValue: "true",
|
|
9178
|
+
exportJsonKey: { aiGenerated: true }
|
|
9179
|
+
}
|
|
9180
|
+
]
|
|
7530
9181
|
},
|
|
7531
9182
|
[BitType.articleAttachment]: {
|
|
7532
9183
|
since: "1.3.0",
|
|
@@ -7851,7 +9502,19 @@ var BITS = {
|
|
|
7851
9502
|
[BitType.noteAi]: {
|
|
7852
9503
|
since: "1.3.0",
|
|
7853
9504
|
baseBitType: BitType.note,
|
|
7854
|
-
description: "Note AI bit, used to create AI-generated notes in articles or books"
|
|
9505
|
+
description: "Note AI bit, used to create AI-generated notes in articles or books",
|
|
9506
|
+
tags: [
|
|
9507
|
+
{
|
|
9508
|
+
// Shadows the inherited @aiGenerated from the standard group: this
|
|
9509
|
+
// bit type forces aiGenerated:true regardless of source value or
|
|
9510
|
+
// absence. Literal `true` in the export pattern ignores `$`.
|
|
9511
|
+
key: ConfigKey.property_aiGenerated,
|
|
9512
|
+
description: "AI-generated flag, forced true for article-ai bits",
|
|
9513
|
+
format: TagFormat.boolean,
|
|
9514
|
+
defaultValue: "true",
|
|
9515
|
+
exportJsonKey: { aiGenerated: true }
|
|
9516
|
+
}
|
|
9517
|
+
]
|
|
7855
9518
|
},
|
|
7856
9519
|
[BitType.notebookArticle]: {
|
|
7857
9520
|
since: "1.3.0",
|
|
@@ -7976,7 +9639,19 @@ var BITS = {
|
|
|
7976
9639
|
[BitType.summaryAi]: {
|
|
7977
9640
|
since: "1.3.0",
|
|
7978
9641
|
baseBitType: BitType.summary,
|
|
7979
|
-
description: "AI-generated summary bit"
|
|
9642
|
+
description: "AI-generated summary bit",
|
|
9643
|
+
tags: [
|
|
9644
|
+
{
|
|
9645
|
+
// Shadows the inherited @aiGenerated from the standard group: this
|
|
9646
|
+
// bit type forces aiGenerated:true regardless of source value or
|
|
9647
|
+
// absence. Literal `true` in the export pattern ignores `$`.
|
|
9648
|
+
key: ConfigKey.property_aiGenerated,
|
|
9649
|
+
description: "AI-generated flag, forced true for article-ai bits",
|
|
9650
|
+
format: TagFormat.boolean,
|
|
9651
|
+
defaultValue: "true",
|
|
9652
|
+
exportJsonKey: { aiGenerated: true }
|
|
9653
|
+
}
|
|
9654
|
+
]
|
|
7980
9655
|
},
|
|
7981
9656
|
[BitType.videoTranscript]: {
|
|
7982
9657
|
since: "1.3.0",
|
|
@@ -8250,7 +9925,7 @@ var BITS = {
|
|
|
8250
9925
|
description: "Product ID for the module product, used to link to a specific product",
|
|
8251
9926
|
format: TagFormat.plainText,
|
|
8252
9927
|
minCount: 1,
|
|
8253
|
-
maxCount:
|
|
9928
|
+
maxCount: 1
|
|
8254
9929
|
}
|
|
8255
9930
|
]
|
|
8256
9931
|
},
|
|
@@ -8303,10 +9978,13 @@ var BITS = {
|
|
|
8303
9978
|
description: "Resource bit tags for logo grave images, used to define additional properties"
|
|
8304
9979
|
},
|
|
8305
9980
|
{
|
|
8306
|
-
// Image resource
|
|
8307
|
-
|
|
9981
|
+
// Image resource — fully-expanded shape so each `&image` emission
|
|
9982
|
+
// produces a complete `{type, image: {src}}` object inside the
|
|
9983
|
+
// `images[]` array. Chain attrs like `[@zoomDisabled]` fold under
|
|
9984
|
+
// `images[].image` via the `[]` last-element-merge semantic.
|
|
9985
|
+
key: ConfigKey.group_resourceImageNoZoom,
|
|
8308
9986
|
description: "Resource image tags for logo grave images, used to attach images",
|
|
8309
|
-
exportJsonKey: { images: "$" },
|
|
9987
|
+
exportJsonKey: { images: [{ type: "image", image: { src: "$" } }] },
|
|
8310
9988
|
minCount: 1,
|
|
8311
9989
|
maxCount: Count.infinity
|
|
8312
9990
|
}
|
|
@@ -8596,6 +10274,10 @@ var BITS = {
|
|
|
8596
10274
|
tags: [
|
|
8597
10275
|
{
|
|
8598
10276
|
key: ConfigKey.property_book,
|
|
10277
|
+
// Starter-array shape: each `[@book:value]` chain appends a fresh
|
|
10278
|
+
// `{ book, reference }` entry. The chained `►` (tag_reference) lands
|
|
10279
|
+
// inside the most-recently-appended entry as `reference: $`.
|
|
10280
|
+
exportJsonKey: { book: [{ book: "$" }] },
|
|
8599
10281
|
description: "Book reference for the page, used to link to a specific book",
|
|
8600
10282
|
maxCount: Count.infinity,
|
|
8601
10283
|
chain: [
|
|
@@ -8663,7 +10345,9 @@ var BITS = {
|
|
|
8663
10345
|
format: TagFormat.plainText
|
|
8664
10346
|
}
|
|
8665
10347
|
],
|
|
8666
|
-
|
|
10348
|
+
// page-footer emits cards under `sections` (vs `listItems` for the
|
|
10349
|
+
// sibling exampleList / assignmentList bits) — see JsonGenerator.ts.
|
|
10350
|
+
cardSet: CardSetConfigKey.pageFooterSections
|
|
8667
10351
|
},
|
|
8668
10352
|
[BitType.legend]: {
|
|
8669
10353
|
since: "3.12.0",
|
|
@@ -8705,13 +10389,52 @@ var BITS = {
|
|
|
8705
10389
|
since: "1.34.0",
|
|
8706
10390
|
baseBitType: BitType._standard,
|
|
8707
10391
|
description: "Definition list bit, used to create lists of definitions in articles or books",
|
|
10392
|
+
tags: [
|
|
10393
|
+
{
|
|
10394
|
+
// Override inherited @example: in `isTopLevelExample` allow-list.
|
|
10395
|
+
key: ConfigKey.property_example,
|
|
10396
|
+
exportJsonKey: [
|
|
10397
|
+
{ "@keyonly": { isExample: true } },
|
|
10398
|
+
{ "@absent": { isExample: true } },
|
|
10399
|
+
{ isExample: true, example: "$" }
|
|
10400
|
+
],
|
|
10401
|
+
description: "The example(s) for the bit",
|
|
10402
|
+
format: TagFormat.bitmarkText,
|
|
10403
|
+
nullable: true
|
|
10404
|
+
}
|
|
10405
|
+
],
|
|
8708
10406
|
cardSet: CardSetConfigKey.definitionList
|
|
8709
10407
|
},
|
|
10408
|
+
[BitType.definitionListCollapsible]: {
|
|
10409
|
+
since: "5.25.0",
|
|
10410
|
+
baseBitType: BitType.definitionList,
|
|
10411
|
+
description: "Collapsible definition list bit, used to create collapsible lists of definitions in articles or books"
|
|
10412
|
+
},
|
|
10413
|
+
[BitType.standardDefinitionListNormative]: {
|
|
10414
|
+
since: "5.24.0",
|
|
10415
|
+
baseBitType: BitType.definitionList,
|
|
10416
|
+
description: "Standard normative definition list bit, used to create normative standard definition lists in articles or books"
|
|
10417
|
+
},
|
|
10418
|
+
[BitType.standardDefinitionListNonNormative]: {
|
|
10419
|
+
since: "5.24.0",
|
|
10420
|
+
baseBitType: BitType.definitionList,
|
|
10421
|
+
description: "Standard non-normative definition list bit, used to create non-normative standard definition lists in articles or books"
|
|
10422
|
+
},
|
|
10423
|
+
[BitType.smartStandardDefinitionListNormative]: {
|
|
10424
|
+
since: "5.24.0",
|
|
10425
|
+
baseBitType: BitType.standardDefinitionListNormative,
|
|
10426
|
+
description: "Smart standard normative definition list bit, used to create normative smart standard definition lists in articles or books"
|
|
10427
|
+
},
|
|
10428
|
+
[BitType.smartStandardDefinitionListNonNormative]: {
|
|
10429
|
+
since: "5.24.0",
|
|
10430
|
+
baseBitType: BitType.standardDefinitionListNonNormative,
|
|
10431
|
+
description: "Smart standard non-normative definition list bit, used to create non-normative smart standard definition lists in articles or books"
|
|
10432
|
+
},
|
|
8710
10433
|
[BitType.metaSearchDefaultTerms]: {
|
|
8711
10434
|
since: "3.12.0",
|
|
8712
10435
|
baseBitType: BitType._standard,
|
|
8713
10436
|
description: "Meta search default terms bit, used to define default search terms in articles",
|
|
8714
|
-
cardSet: CardSetConfigKey.
|
|
10437
|
+
cardSet: CardSetConfigKey.definitionListPlain
|
|
8715
10438
|
},
|
|
8716
10439
|
[BitType.metaSearchDefaultTopics]: {
|
|
8717
10440
|
since: "3.12.0",
|
|
@@ -8727,6 +10450,18 @@ var BITS = {
|
|
|
8727
10450
|
{
|
|
8728
10451
|
key: ConfigKey.group_quizCommon,
|
|
8729
10452
|
description: "Common quiz tags for flashcards"
|
|
10453
|
+
},
|
|
10454
|
+
{
|
|
10455
|
+
// Override inherited @example: in `isTopLevelExample` allow-list.
|
|
10456
|
+
key: ConfigKey.property_example,
|
|
10457
|
+
exportJsonKey: [
|
|
10458
|
+
{ "@keyonly": { isExample: true } },
|
|
10459
|
+
{ "@absent": { isExample: true } },
|
|
10460
|
+
{ isExample: true, example: "$" }
|
|
10461
|
+
],
|
|
10462
|
+
description: "The example(s) for the bit",
|
|
10463
|
+
format: TagFormat.bitmarkText,
|
|
10464
|
+
nullable: true
|
|
8730
10465
|
}
|
|
8731
10466
|
],
|
|
8732
10467
|
cardSet: CardSetConfigKey.flashcard
|
|
@@ -8734,12 +10469,20 @@ var BITS = {
|
|
|
8734
10469
|
[BitType.flashcard1]: {
|
|
8735
10470
|
since: "1.3.0",
|
|
8736
10471
|
baseBitType: BitType.flashcard,
|
|
8737
|
-
description: "Flashcard 1 bit"
|
|
10472
|
+
description: "Flashcard 1 bit",
|
|
10473
|
+
// PLAN-085: use the flashcard1 cardset (sections.default.maxCount=1)
|
|
10474
|
+
// instead of inheriting `flashcard`'s unbounded cardset, since this
|
|
10475
|
+
// bit type structurally allows only a single card.
|
|
10476
|
+
cardSet: CardSetConfigKey.flashcard1
|
|
8738
10477
|
},
|
|
8739
10478
|
[BitType.qAndACard]: {
|
|
8740
10479
|
since: "3.25.0",
|
|
8741
10480
|
baseBitType: BitType.flashcard1,
|
|
8742
|
-
description: "Q&A card bit, used to create question and answer cards in articles or books"
|
|
10481
|
+
description: "Q&A card bit, used to create question and answer cards in articles or books",
|
|
10482
|
+
// PLAN-085: q-and-a-card permits multiple cards (some fixtures carry
|
|
10483
|
+
// two). Override the inherited `flashcard1` (max=1) cardset back to
|
|
10484
|
+
// the unbounded `flashcard` cardset.
|
|
10485
|
+
cardSet: CardSetConfigKey.flashcard
|
|
8743
10486
|
},
|
|
8744
10487
|
[BitType.focusImage]: {
|
|
8745
10488
|
since: "1.3.0",
|
|
@@ -8769,8 +10512,24 @@ var BITS = {
|
|
|
8769
10512
|
description: "Common quiz tags for highlighted text"
|
|
8770
10513
|
},
|
|
8771
10514
|
{
|
|
8772
|
-
|
|
8773
|
-
|
|
10515
|
+
// Dedicated highlight inline-select chain: tag_true / tag_false
|
|
10516
|
+
// emit `{type:"highlight", attrs:{texts:[{text,isCorrect?}]}}`
|
|
10517
|
+
// inline body nodes via @body-inline. Bit shares no tag IDs with
|
|
10518
|
+
// multiple-choice / multiple-choice-1 (which use group_trueFalse).
|
|
10519
|
+
key: ConfigKey.group_trueFalseInlineHighlight,
|
|
10520
|
+
description: "True/False quiz tags for highlighted text (inline body nodes)"
|
|
10521
|
+
},
|
|
10522
|
+
{
|
|
10523
|
+
// Override inherited @example: in `isTopLevelExample` allow-list.
|
|
10524
|
+
key: ConfigKey.property_example,
|
|
10525
|
+
exportJsonKey: [
|
|
10526
|
+
{ "@keyonly": { isExample: true } },
|
|
10527
|
+
{ "@absent": { isExample: true } },
|
|
10528
|
+
{ isExample: true, example: "$" }
|
|
10529
|
+
],
|
|
10530
|
+
description: "The example(s) for the bit",
|
|
10531
|
+
format: TagFormat.bitmarkText,
|
|
10532
|
+
nullable: true
|
|
8774
10533
|
}
|
|
8775
10534
|
]
|
|
8776
10535
|
},
|
|
@@ -8780,15 +10539,8 @@ var BITS = {
|
|
|
8780
10539
|
description: "Image bit, used to create images in articles or books",
|
|
8781
10540
|
tags: [
|
|
8782
10541
|
{
|
|
8783
|
-
key: ConfigKey.
|
|
8784
|
-
|
|
8785
|
-
description: "Background wallpaper for the image, used to set a background for the image",
|
|
8786
|
-
chain: [
|
|
8787
|
-
{
|
|
8788
|
-
key: ConfigKey.group_resourceImageCommon,
|
|
8789
|
-
description: "Common resource image tags for images"
|
|
8790
|
-
}
|
|
8791
|
-
]
|
|
10542
|
+
key: ConfigKey.group_backgroundWallpaper,
|
|
10543
|
+
description: "Background wallpaper tags for images, used to define background properties for images"
|
|
8792
10544
|
},
|
|
8793
10545
|
{
|
|
8794
10546
|
key: ConfigKey.group_resourceBitTags,
|
|
@@ -8891,8 +10643,15 @@ var BITS = {
|
|
|
8891
10643
|
},
|
|
8892
10644
|
[BitType.imageSeparator]: {
|
|
8893
10645
|
since: "1.4.15",
|
|
8894
|
-
baseBitType: BitType.
|
|
8895
|
-
description: "Image separator bit, used to create separators in articles or books"
|
|
10646
|
+
baseBitType: BitType._standard,
|
|
10647
|
+
description: "Image separator bit, used to create separators in articles or books",
|
|
10648
|
+
tags: [
|
|
10649
|
+
{
|
|
10650
|
+
key: ConfigKey.group_imageNoZoom,
|
|
10651
|
+
description: "Image no zoom tags for images, used to define whether images should have zoom functionality"
|
|
10652
|
+
}
|
|
10653
|
+
],
|
|
10654
|
+
resourceAttachmentAllowed: false
|
|
8896
10655
|
},
|
|
8897
10656
|
[BitType.imageSeparatorAlt]: {
|
|
8898
10657
|
since: "1.16.0",
|
|
@@ -8931,15 +10690,20 @@ var BITS = {
|
|
|
8931
10690
|
},
|
|
8932
10691
|
[BitType.pageBanner]: {
|
|
8933
10692
|
since: "1.4.3",
|
|
8934
|
-
baseBitType: BitType.
|
|
10693
|
+
baseBitType: BitType._standard,
|
|
8935
10694
|
description: "Page banner bit, used to create banners for pages in articles or books",
|
|
8936
10695
|
tags: [
|
|
8937
10696
|
{
|
|
8938
10697
|
key: ConfigKey.property_slug,
|
|
8939
10698
|
description: "Slug for the page banner, used to identify the banner in the system",
|
|
8940
10699
|
format: TagFormat.plainText
|
|
10700
|
+
},
|
|
10701
|
+
{
|
|
10702
|
+
key: ConfigKey.group_imageNoZoom,
|
|
10703
|
+
description: "Image no zoom tags for images, used to define whether images should have zoom functionality"
|
|
8941
10704
|
}
|
|
8942
|
-
]
|
|
10705
|
+
],
|
|
10706
|
+
resourceAttachmentAllowed: false
|
|
8943
10707
|
},
|
|
8944
10708
|
[BitType.pageHero]: {
|
|
8945
10709
|
since: "1.11.0",
|
|
@@ -8954,7 +10718,7 @@ var BITS = {
|
|
|
8954
10718
|
},
|
|
8955
10719
|
[BitType.tableImage]: {
|
|
8956
10720
|
since: "1.5.15",
|
|
8957
|
-
baseBitType: BitType.
|
|
10721
|
+
baseBitType: BitType.tableExtended,
|
|
8958
10722
|
description: "Table image bit, used to create images in tables in articles or books",
|
|
8959
10723
|
tags: [
|
|
8960
10724
|
{
|
|
@@ -8963,15 +10727,8 @@ var BITS = {
|
|
|
8963
10727
|
format: TagFormat.bitmarkText
|
|
8964
10728
|
},
|
|
8965
10729
|
{
|
|
8966
|
-
key: ConfigKey.
|
|
8967
|
-
|
|
8968
|
-
description: "Background wallpaper for the image, used to set a background for the image",
|
|
8969
|
-
chain: [
|
|
8970
|
-
{
|
|
8971
|
-
key: ConfigKey.group_resourceImageCommon,
|
|
8972
|
-
description: "Common resource image tags for images"
|
|
8973
|
-
}
|
|
8974
|
-
]
|
|
10730
|
+
key: ConfigKey.group_backgroundWallpaper,
|
|
10731
|
+
description: "Background wallpaper tags for images, used to define background properties for images"
|
|
8975
10732
|
},
|
|
8976
10733
|
{
|
|
8977
10734
|
key: ConfigKey.group_resourceBitTags,
|
|
@@ -9114,10 +10871,10 @@ var BITS = {
|
|
|
9114
10871
|
description: "Resource bit tags for logo grave images, used to define additional properties"
|
|
9115
10872
|
},
|
|
9116
10873
|
{
|
|
9117
|
-
// Image resource
|
|
9118
|
-
key: ConfigKey.
|
|
10874
|
+
// Image resource — fully-expanded shape (see BitType.extractorImage).
|
|
10875
|
+
key: ConfigKey.group_resourceImageNoZoom,
|
|
9119
10876
|
description: "Resource image tags for logo grave images, used to attach images",
|
|
9120
|
-
exportJsonKey: { logos: "$" },
|
|
10877
|
+
exportJsonKey: { logos: [{ type: "image", image: { src: "$" } }] },
|
|
9121
10878
|
minCount: 1,
|
|
9122
10879
|
maxCount: Count.infinity
|
|
9123
10880
|
}
|
|
@@ -9127,7 +10884,17 @@ var BITS = {
|
|
|
9127
10884
|
[BitType.prototypeImages]: {
|
|
9128
10885
|
since: "1.6.1",
|
|
9129
10886
|
baseBitType: BitType.imagesLogoGrave,
|
|
9130
|
-
description: "Prototype images bit, used to create prototype images in articles or books"
|
|
10887
|
+
description: "Prototype images bit, used to create prototype images in articles or books",
|
|
10888
|
+
tags: [
|
|
10889
|
+
{
|
|
10890
|
+
// Override inherited `logos` wrapper with `images` for this bit type.
|
|
10891
|
+
key: ConfigKey.group_resourceImageNoZoom,
|
|
10892
|
+
description: "Resource image tags for prototype images",
|
|
10893
|
+
exportJsonKey: { images: [{ type: "image", image: { src: "$" } }] },
|
|
10894
|
+
minCount: 1,
|
|
10895
|
+
maxCount: Count.infinity
|
|
10896
|
+
}
|
|
10897
|
+
]
|
|
9131
10898
|
},
|
|
9132
10899
|
[BitType.internalLink]: {
|
|
9133
10900
|
since: "1.3.0",
|
|
@@ -9157,6 +10924,18 @@ var BITS = {
|
|
|
9157
10924
|
exportJsonKey: {},
|
|
9158
10925
|
description: "Reasonable number of characters for the interview, used to limit input size",
|
|
9159
10926
|
format: TagFormat.number
|
|
10927
|
+
},
|
|
10928
|
+
{
|
|
10929
|
+
// Override inherited @example: in `isTopLevelExample` allow-list.
|
|
10930
|
+
key: ConfigKey.property_example,
|
|
10931
|
+
exportJsonKey: [
|
|
10932
|
+
{ "@keyonly": { isExample: true } },
|
|
10933
|
+
{ "@absent": { isExample: true } },
|
|
10934
|
+
{ isExample: true, example: "$" }
|
|
10935
|
+
],
|
|
10936
|
+
description: "The example(s) for the bit",
|
|
10937
|
+
format: TagFormat.bitmarkText,
|
|
10938
|
+
nullable: true
|
|
9160
10939
|
}
|
|
9161
10940
|
],
|
|
9162
10941
|
cardSet: CardSetConfigKey.questions
|
|
@@ -9515,6 +11294,18 @@ var BITS = {
|
|
|
9515
11294
|
{
|
|
9516
11295
|
key: ConfigKey.group_mark,
|
|
9517
11296
|
description: "Mark tags, used to define the content of marks"
|
|
11297
|
+
},
|
|
11298
|
+
{
|
|
11299
|
+
// Override inherited @example: in `isTopLevelExample` allow-list.
|
|
11300
|
+
key: ConfigKey.property_example,
|
|
11301
|
+
exportJsonKey: [
|
|
11302
|
+
{ "@keyonly": { isExample: true } },
|
|
11303
|
+
{ "@absent": { isExample: true } },
|
|
11304
|
+
{ isExample: true, example: "$" }
|
|
11305
|
+
],
|
|
11306
|
+
description: "The example(s) for the bit",
|
|
11307
|
+
format: TagFormat.bitmarkText,
|
|
11308
|
+
nullable: true
|
|
9518
11309
|
}
|
|
9519
11310
|
]
|
|
9520
11311
|
},
|
|
@@ -9534,6 +11325,23 @@ var BITS = {
|
|
|
9534
11325
|
description: "Case sensitivity for matching pairs, used to define if matches are case-sensitive",
|
|
9535
11326
|
format: TagFormat.boolean,
|
|
9536
11327
|
defaultValue: "true"
|
|
11328
|
+
},
|
|
11329
|
+
{
|
|
11330
|
+
// Override inherited @example: in `isTopLevelExample` allow-list
|
|
11331
|
+
// (covers match, matchAll, matchReverse, matchAllReverse,
|
|
11332
|
+
// matchSolutionGrouped, matchMatrix, matchAudio, matchPicture).
|
|
11333
|
+
// Bit-level fires emit only `isExample`; the @example value (when
|
|
11334
|
+
// provided) is recorded in the ancestor stack and consumed by
|
|
11335
|
+
// per-pair/per-cell variant overrides — not surfaced at bit scope.
|
|
11336
|
+
key: ConfigKey.property_example,
|
|
11337
|
+
exportJsonKey: [
|
|
11338
|
+
{ "@keyonly": { isExample: true } },
|
|
11339
|
+
{ "@absent": { isExample: true } },
|
|
11340
|
+
{ isExample: true }
|
|
11341
|
+
],
|
|
11342
|
+
description: "The example(s) for the bit",
|
|
11343
|
+
format: TagFormat.bitmarkText,
|
|
11344
|
+
nullable: true
|
|
9537
11345
|
}
|
|
9538
11346
|
],
|
|
9539
11347
|
cardSet: CardSetConfigKey.matchPairs
|
|
@@ -9618,6 +11426,18 @@ var BITS = {
|
|
|
9618
11426
|
{
|
|
9619
11427
|
key: ConfigKey.group_trueFalse,
|
|
9620
11428
|
description: "True/False tags for multiple choice 1 questions"
|
|
11429
|
+
},
|
|
11430
|
+
{
|
|
11431
|
+
// Override inherited @example: in `isTopLevelExample` allow-list.
|
|
11432
|
+
key: ConfigKey.property_example,
|
|
11433
|
+
exportJsonKey: [
|
|
11434
|
+
{ "@keyonly": { isExample: true } },
|
|
11435
|
+
{ "@absent": { isExample: true } },
|
|
11436
|
+
{ isExample: true, example: "$" }
|
|
11437
|
+
],
|
|
11438
|
+
description: "The example(s) for the bit",
|
|
11439
|
+
format: TagFormat.bitmarkText,
|
|
11440
|
+
nullable: true
|
|
9621
11441
|
}
|
|
9622
11442
|
]
|
|
9623
11443
|
},
|
|
@@ -9639,8 +11459,20 @@ var BITS = {
|
|
|
9639
11459
|
{
|
|
9640
11460
|
key: ConfigKey.group_trueFalse,
|
|
9641
11461
|
description: "True/False tags for multiple choice questions"
|
|
9642
|
-
}
|
|
11462
|
+
},
|
|
9643
11463
|
// This is actually for multiple-choice-1, but we support it here as well (as many bits are wrong)
|
|
11464
|
+
{
|
|
11465
|
+
// Override inherited @example: in `isTopLevelExample` allow-list.
|
|
11466
|
+
key: ConfigKey.property_example,
|
|
11467
|
+
exportJsonKey: [
|
|
11468
|
+
{ "@keyonly": { isExample: true } },
|
|
11469
|
+
{ "@absent": { isExample: true } },
|
|
11470
|
+
{ isExample: true, example: "$" }
|
|
11471
|
+
],
|
|
11472
|
+
description: "The example(s) for the bit",
|
|
11473
|
+
format: TagFormat.bitmarkText,
|
|
11474
|
+
nullable: true
|
|
11475
|
+
}
|
|
9644
11476
|
],
|
|
9645
11477
|
cardSet: CardSetConfigKey.quiz
|
|
9646
11478
|
},
|
|
@@ -9660,8 +11492,20 @@ var BITS = {
|
|
|
9660
11492
|
description: "Common quiz tags for multiple choice text questions"
|
|
9661
11493
|
},
|
|
9662
11494
|
{
|
|
9663
|
-
key: ConfigKey.
|
|
11495
|
+
key: ConfigKey.group_trueFalseInlineSelect,
|
|
9664
11496
|
description: "True/False tags for multiple choice text questions"
|
|
11497
|
+
},
|
|
11498
|
+
{
|
|
11499
|
+
// Override inherited @example: in `isTopLevelExample` allow-list.
|
|
11500
|
+
key: ConfigKey.property_example,
|
|
11501
|
+
exportJsonKey: [
|
|
11502
|
+
{ "@keyonly": { isExample: true } },
|
|
11503
|
+
{ "@absent": { isExample: true } },
|
|
11504
|
+
{ isExample: true, example: "$" }
|
|
11505
|
+
],
|
|
11506
|
+
description: "The example(s) for the bit",
|
|
11507
|
+
format: TagFormat.bitmarkText,
|
|
11508
|
+
nullable: true
|
|
9665
11509
|
}
|
|
9666
11510
|
]
|
|
9667
11511
|
},
|
|
@@ -9686,8 +11530,20 @@ var BITS = {
|
|
|
9686
11530
|
description: "Common quiz tags for multiple response 1 questions"
|
|
9687
11531
|
},
|
|
9688
11532
|
{
|
|
9689
|
-
key: ConfigKey.
|
|
11533
|
+
key: ConfigKey.group_trueFalseResponses,
|
|
9690
11534
|
description: "True/False tags for multiple response 1 questions"
|
|
11535
|
+
},
|
|
11536
|
+
{
|
|
11537
|
+
// Override inherited @example: in `isTopLevelExample` allow-list.
|
|
11538
|
+
key: ConfigKey.property_example,
|
|
11539
|
+
exportJsonKey: [
|
|
11540
|
+
{ "@keyonly": { isExample: true } },
|
|
11541
|
+
{ "@absent": { isExample: true } },
|
|
11542
|
+
{ isExample: true, example: "$" }
|
|
11543
|
+
],
|
|
11544
|
+
description: "The example(s) for the bit",
|
|
11545
|
+
format: TagFormat.bitmarkText,
|
|
11546
|
+
nullable: true
|
|
9691
11547
|
}
|
|
9692
11548
|
]
|
|
9693
11549
|
},
|
|
@@ -9707,12 +11563,24 @@ var BITS = {
|
|
|
9707
11563
|
description: "Common quiz tags for multiple response questions"
|
|
9708
11564
|
},
|
|
9709
11565
|
{
|
|
9710
|
-
key: ConfigKey.
|
|
11566
|
+
key: ConfigKey.group_trueFalseResponses,
|
|
9711
11567
|
description: "True/False tags for multiple response questions"
|
|
9712
|
-
}
|
|
11568
|
+
},
|
|
9713
11569
|
// This is actually for multiple-response-1, but we support it here as well (as many bits are wrong)
|
|
11570
|
+
{
|
|
11571
|
+
// Override inherited @example: in `isTopLevelExample` allow-list.
|
|
11572
|
+
key: ConfigKey.property_example,
|
|
11573
|
+
exportJsonKey: [
|
|
11574
|
+
{ "@keyonly": { isExample: true } },
|
|
11575
|
+
{ "@absent": { isExample: true } },
|
|
11576
|
+
{ isExample: true, example: "$" }
|
|
11577
|
+
],
|
|
11578
|
+
description: "The example(s) for the bit",
|
|
11579
|
+
format: TagFormat.bitmarkText,
|
|
11580
|
+
nullable: true
|
|
11581
|
+
}
|
|
9714
11582
|
],
|
|
9715
|
-
cardSet: CardSetConfigKey.
|
|
11583
|
+
cardSet: CardSetConfigKey.quizResponses
|
|
9716
11584
|
},
|
|
9717
11585
|
[BitType.coachSelfReflectionMultipleResponse]: {
|
|
9718
11586
|
since: "1.3.0",
|
|
@@ -9962,6 +11830,8 @@ var BITS = {
|
|
|
9962
11830
|
},
|
|
9963
11831
|
{
|
|
9964
11832
|
key: ConfigKey.resource_platformIcon,
|
|
11833
|
+
jsonKey: "platformIcon|resource(type=image, key=image)",
|
|
11834
|
+
exportJsonKey: { platformIcon: { type: "image", image: { src: "$" } } },
|
|
9965
11835
|
description: "The platform icon",
|
|
9966
11836
|
chain: [
|
|
9967
11837
|
{
|
|
@@ -9997,6 +11867,8 @@ var BITS = {
|
|
|
9997
11867
|
tags: [
|
|
9998
11868
|
{
|
|
9999
11869
|
key: ConfigKey.resource_platformLogo,
|
|
11870
|
+
jsonKey: "platformLogo|resource(type=image, key=image)",
|
|
11871
|
+
exportJsonKey: { platformLogo: { type: "image", image: { src: "$" } } },
|
|
10000
11872
|
description: "The platform logo",
|
|
10001
11873
|
chain: [
|
|
10002
11874
|
{
|
|
@@ -10208,7 +12080,8 @@ var BITS = {
|
|
|
10208
12080
|
{ isExample: true, example: "$" }
|
|
10209
12081
|
],
|
|
10210
12082
|
description: "The example text for the review note bit",
|
|
10211
|
-
format: TagFormat.plainText
|
|
12083
|
+
format: TagFormat.plainText,
|
|
12084
|
+
nullable: true
|
|
10212
12085
|
}
|
|
10213
12086
|
],
|
|
10214
12087
|
rootExampleType: ExampleType.string
|
|
@@ -10295,7 +12168,8 @@ var BITS = {
|
|
|
10295
12168
|
{ isExample: true, example: "$" }
|
|
10296
12169
|
],
|
|
10297
12170
|
description: "The example flag for the bit (boolean)",
|
|
10298
|
-
format: TagFormat.
|
|
12171
|
+
format: TagFormat.boolean,
|
|
12172
|
+
nullable: true
|
|
10299
12173
|
}
|
|
10300
12174
|
],
|
|
10301
12175
|
cardSet: CardSetConfigKey.elements,
|
|
@@ -10569,15 +12443,8 @@ var BITS = {
|
|
|
10569
12443
|
format: TagFormat.bitmarkText
|
|
10570
12444
|
},
|
|
10571
12445
|
{
|
|
10572
|
-
key: ConfigKey.
|
|
10573
|
-
|
|
10574
|
-
description: "Background wallpaper for the image, used to set a background for the image",
|
|
10575
|
-
chain: [
|
|
10576
|
-
{
|
|
10577
|
-
key: ConfigKey.group_resourceImageCommon,
|
|
10578
|
-
description: "Common resource image tags for images"
|
|
10579
|
-
}
|
|
10580
|
-
]
|
|
12446
|
+
key: ConfigKey.group_backgroundWallpaper,
|
|
12447
|
+
description: "Background wallpaper tags for images, used to define background properties for images"
|
|
10581
12448
|
},
|
|
10582
12449
|
{
|
|
10583
12450
|
key: ConfigKey.group_resourceBitTags,
|
|
@@ -10954,16 +12821,20 @@ var BITS = {
|
|
|
10954
12821
|
format: TagFormat.plainText
|
|
10955
12822
|
},
|
|
10956
12823
|
{
|
|
10957
|
-
|
|
12824
|
+
// PLAN-067 Edit #1: true-false-1's `+` / `-` emit a flat
|
|
12825
|
+
// `{ statement, isCorrect }` at bit root (single-statement bit),
|
|
12826
|
+
// not a `choices[]` array. Format is bitmarkText so the statement
|
|
12827
|
+
// body renders as a ProseMirror tree, matching reference fixtures.
|
|
12828
|
+
exportJsonKey: { statement: "$", isCorrect: true },
|
|
10958
12829
|
key: ConfigKey.tag_true,
|
|
10959
12830
|
description: "Tag for the true option in the true/false question",
|
|
10960
|
-
format: TagFormat.
|
|
12831
|
+
format: TagFormat.bitmarkText
|
|
10961
12832
|
},
|
|
10962
12833
|
{
|
|
10963
|
-
exportJsonKey: {
|
|
12834
|
+
exportJsonKey: { statement: "$", isCorrect: false },
|
|
10964
12835
|
key: ConfigKey.tag_false,
|
|
10965
12836
|
description: "Tag for the false option in the true/false question",
|
|
10966
|
-
format: TagFormat.
|
|
12837
|
+
format: TagFormat.bitmarkText
|
|
10967
12838
|
},
|
|
10968
12839
|
{
|
|
10969
12840
|
// Override inherited @example from group_standardTags: boolean root
|
|
@@ -10974,14 +12845,24 @@ var BITS = {
|
|
|
10974
12845
|
// sufficient for `[+...]` (correct) statements which dominate the
|
|
10975
12846
|
// fixtures; `[-...]` (incorrect) bits with bare `[@example]` would
|
|
10976
12847
|
// need either a runtime extension or an explicit `[@example:false]`.
|
|
12848
|
+
//
|
|
12849
|
+
// Format is `boolean` so `[@example:false]` coerces to JSON `false`
|
|
12850
|
+
// (a natural default that gets stripped in optimised mode, matching
|
|
12851
|
+
// BPG's `rootExampleType: boolean`). PlainText would emit the
|
|
12852
|
+
// STRING `"false"` which doesn't strip.
|
|
10977
12853
|
key: ConfigKey.property_example,
|
|
10978
12854
|
exportJsonKey: [
|
|
10979
12855
|
{ "@keyonly": { isExample: true, example: true } },
|
|
10980
12856
|
{ "@absent": { isExample: true, example: true } },
|
|
12857
|
+
// `[@example:false]`: emit `isExample: true` only — `example: false`
|
|
12858
|
+
// is the natural default for boolean, stripped in optimised mode.
|
|
12859
|
+
// Mirrors BPG `removeUnwantedProperties` `ignoreAllFalse: true`.
|
|
12860
|
+
{ "@value=false": { isExample: true } },
|
|
10981
12861
|
{ isExample: true, example: "$" }
|
|
10982
12862
|
],
|
|
10983
12863
|
description: "The example flag for the bit (boolean)",
|
|
10984
|
-
format: TagFormat.
|
|
12864
|
+
format: TagFormat.boolean,
|
|
12865
|
+
nullable: true
|
|
10985
12866
|
}
|
|
10986
12867
|
],
|
|
10987
12868
|
rootExampleType: ExampleType.boolean
|
|
@@ -11005,6 +12886,18 @@ var BITS = {
|
|
|
11005
12886
|
key: ConfigKey.property_labelFalse,
|
|
11006
12887
|
description: "Label for the false option in the true/false question",
|
|
11007
12888
|
format: TagFormat.plainText
|
|
12889
|
+
},
|
|
12890
|
+
{
|
|
12891
|
+
// Override inherited @example: in `isTopLevelExample` allow-list.
|
|
12892
|
+
key: ConfigKey.property_example,
|
|
12893
|
+
exportJsonKey: [
|
|
12894
|
+
{ "@keyonly": { isExample: true } },
|
|
12895
|
+
{ "@absent": { isExample: true } },
|
|
12896
|
+
{ isExample: true, example: "$" }
|
|
12897
|
+
],
|
|
12898
|
+
description: "The example(s) for the bit",
|
|
12899
|
+
format: TagFormat.bitmarkText,
|
|
12900
|
+
nullable: true
|
|
11008
12901
|
}
|
|
11009
12902
|
],
|
|
11010
12903
|
cardSet: CardSetConfigKey.statements
|
|
@@ -11194,6 +13087,11 @@ var BITS = {
|
|
|
11194
13087
|
baseBitType: BitType.vendorStripePricingTable,
|
|
11195
13088
|
description: "Stripe pricing table bit with print-on-request customer/product IDs, used to embed Stripe pricing tables tied to a specific print-on-request customer and product",
|
|
11196
13089
|
tags: [
|
|
13090
|
+
{
|
|
13091
|
+
key: ConfigKey.property_stripePricingTableDescriptiveName,
|
|
13092
|
+
description: "Descriptive name for the Stripe pricing table",
|
|
13093
|
+
format: TagFormat.plainText
|
|
13094
|
+
},
|
|
11197
13095
|
{
|
|
11198
13096
|
key: ConfigKey.property_printOnRequestCustomerId,
|
|
11199
13097
|
description: "Print-on-request customer ID, used to identify the customer for the print-on-request flow",
|
|
@@ -11670,7 +13568,7 @@ var instance2 = new Config();
|
|
|
11670
13568
|
// src/generated/package_info.ts
|
|
11671
13569
|
var PACKAGE_INFO = {
|
|
11672
13570
|
"name": "@gmb/bitmark-parser-generator",
|
|
11673
|
-
"version": "5.
|
|
13571
|
+
"version": "5.25.0",
|
|
11674
13572
|
"license": "ISC"};
|
|
11675
13573
|
var Environment = {
|
|
11676
13574
|
unknown: "",
|
|
@@ -12284,6 +14182,8 @@ var NodeType = {
|
|
|
12284
14182
|
internalPrintPdf: "internalPrintPdf",
|
|
12285
14183
|
internalPrintPdfValue: "internalPrintPdfValue",
|
|
12286
14184
|
isCaseSensitive: "isCaseSensitive",
|
|
14185
|
+
isCollapsible: "isCollapsible",
|
|
14186
|
+
isCollapsibleValue: "isCollapsibleValue",
|
|
12287
14187
|
isCommented: "isCommented",
|
|
12288
14188
|
isCorrect: "isCorrect",
|
|
12289
14189
|
isEditable: "isEditable",
|
|
@@ -12603,6 +14503,8 @@ var NodeType = {
|
|
|
12603
14503
|
stillImageFilmLink: "stillImageFilmLink",
|
|
12604
14504
|
stripePricingTableId: "stripePricingTableId",
|
|
12605
14505
|
stripePricingTableIdValue: "stripePricingTableIdValue",
|
|
14506
|
+
stripePricingTableDescriptiveName: "stripePricingTableDescriptiveName",
|
|
14507
|
+
stripePricingTableDescriptiveNameValue: "stripePricingTableDescriptiveNameValue",
|
|
12606
14508
|
stripePublishableKey: "stripePublishableKey",
|
|
12607
14509
|
stripePublishableKeyValue: "stripePublishableKeyValue",
|
|
12608
14510
|
subject: "subject",
|
|
@@ -25844,6 +27746,12 @@ var Builder = class extends BaseBuilder {
|
|
|
25844
27746
|
data.stripePricingTableId,
|
|
25845
27747
|
options
|
|
25846
27748
|
),
|
|
27749
|
+
stripePricingTableDescriptiveName: this.toAstProperty(
|
|
27750
|
+
bitType,
|
|
27751
|
+
ConfigKey.property_stripePricingTableDescriptiveName,
|
|
27752
|
+
data.stripePricingTableDescriptiveName,
|
|
27753
|
+
options
|
|
27754
|
+
),
|
|
25847
27755
|
stripePublishableKey: this.toAstProperty(
|
|
25848
27756
|
bitType,
|
|
25849
27757
|
ConfigKey.property_stripePublishableKey,
|
|
@@ -26330,6 +28238,12 @@ var Builder = class extends BaseBuilder {
|
|
|
26330
28238
|
level: instance6.asNumber(data.level),
|
|
26331
28239
|
toc: this.toAstProperty(bitType, ConfigKey.property_toc, data.toc, options),
|
|
26332
28240
|
progress: this.toAstProperty(bitType, ConfigKey.property_progress, data.progress, options),
|
|
28241
|
+
isCollapsible: this.toAstProperty(
|
|
28242
|
+
bitType,
|
|
28243
|
+
ConfigKey.property_isCollapsible,
|
|
28244
|
+
data.isCollapsible,
|
|
28245
|
+
options
|
|
28246
|
+
),
|
|
26333
28247
|
anchor: data.anchor,
|
|
26334
28248
|
reference: data.reference,
|
|
26335
28249
|
referenceEnd: data.referenceEnd,
|
|
@@ -31851,6 +33765,7 @@ var JsonGenerator = class extends AstWalkerGenerator {
|
|
|
31851
33765
|
if (instance2.isOfBitType(bitType, BitType.chapter)) {
|
|
31852
33766
|
if (bitJson.toc == null) bitJson.toc = true;
|
|
31853
33767
|
if (bitJson.progress == null) bitJson.progress = true;
|
|
33768
|
+
if (bitJson.isCollapsible == null) bitJson.isCollapsible = false;
|
|
31854
33769
|
if (bitJson.level == null) bitJson.level = 1;
|
|
31855
33770
|
if (bitJson.body == null) bitJson.body = this.bodyDefault;
|
|
31856
33771
|
}
|
|
@@ -33996,7 +35911,9 @@ function buildCards(context, bitType, textFormat, parsedCardSet, statementV1, st
|
|
|
33996
35911
|
const cardSetType = bitConfig.cardSet?.configKey;
|
|
33997
35912
|
switch (cardSetType) {
|
|
33998
35913
|
case CardSetConfigKey.flashcard:
|
|
35914
|
+
case CardSetConfigKey.flashcard1:
|
|
33999
35915
|
case CardSetConfigKey.definitionList:
|
|
35916
|
+
case CardSetConfigKey.definitionListPlain:
|
|
34000
35917
|
result = parseFlashcardOrDefinitionList(context, bitType, processedCardSet);
|
|
34001
35918
|
break;
|
|
34002
35919
|
case CardSetConfigKey.elements:
|
|
@@ -34009,6 +35926,7 @@ function buildCards(context, bitType, textFormat, parsedCardSet, statementV1, st
|
|
|
34009
35926
|
result = parseFeedback(context, bitType, processedCardSet);
|
|
34010
35927
|
break;
|
|
34011
35928
|
case CardSetConfigKey.quiz:
|
|
35929
|
+
case CardSetConfigKey.quizResponses:
|
|
34012
35930
|
result = parseQuiz(context, bitType, processedCardSet, choicesV1, responsesV1);
|
|
34013
35931
|
break;
|
|
34014
35932
|
case CardSetConfigKey.questions:
|
|
@@ -34035,6 +35953,7 @@ function buildCards(context, bitType, textFormat, parsedCardSet, statementV1, st
|
|
|
34035
35953
|
break;
|
|
34036
35954
|
case CardSetConfigKey.clozeList:
|
|
34037
35955
|
case CardSetConfigKey.exampleBitList:
|
|
35956
|
+
case CardSetConfigKey.pageFooterSections:
|
|
34038
35957
|
case CardSetConfigKey.bookReferenceList:
|
|
34039
35958
|
result = parseCardBits(context, bitType, textFormat, processedCardSet);
|
|
34040
35959
|
break;
|
|
@@ -40068,8 +41987,8 @@ var FileWriter = class extends StreamWriter {
|
|
|
40068
41987
|
async open() {
|
|
40069
41988
|
return new Promise((resolve, reject) => {
|
|
40070
41989
|
const flags = this._append ? "a" : "w";
|
|
40071
|
-
|
|
40072
|
-
const stream =
|
|
41990
|
+
fs4.ensureDirSync(path3.dirname(this._path.toString()));
|
|
41991
|
+
const stream = fs4.createWriteStream(this._path, {
|
|
40073
41992
|
flags,
|
|
40074
41993
|
encoding: "utf8"
|
|
40075
41994
|
});
|
|
@@ -40143,8 +42062,8 @@ var SyncFileWriter = class {
|
|
|
40143
42062
|
}
|
|
40144
42063
|
openSync() {
|
|
40145
42064
|
const flag = this._append ? "a" : "w";
|
|
40146
|
-
|
|
40147
|
-
|
|
42065
|
+
fs4.ensureDirSync(path3.dirname(this._path.toString()));
|
|
42066
|
+
fs4.writeFileSync(this._path, "", {
|
|
40148
42067
|
flag,
|
|
40149
42068
|
encoding: this._encoding
|
|
40150
42069
|
});
|
|
@@ -40161,7 +42080,7 @@ var SyncFileWriter = class {
|
|
|
40161
42080
|
} else {
|
|
40162
42081
|
line = this.endOfLineString;
|
|
40163
42082
|
}
|
|
40164
|
-
|
|
42083
|
+
fs4.appendFileSync(this._path, line, {
|
|
40165
42084
|
encoding: this._encoding
|
|
40166
42085
|
});
|
|
40167
42086
|
this.lastWrite = line;
|
|
@@ -40179,7 +42098,7 @@ var SyncFileWriter = class {
|
|
|
40179
42098
|
}
|
|
40180
42099
|
str += this.endOfLineString;
|
|
40181
42100
|
}
|
|
40182
|
-
|
|
42101
|
+
fs4.appendFileSync(this._path, str, {
|
|
40183
42102
|
encoding: this._encoding
|
|
40184
42103
|
});
|
|
40185
42104
|
this.lastWrite = str;
|
|
@@ -40189,7 +42108,7 @@ var SyncFileWriter = class {
|
|
|
40189
42108
|
if (!this._open) return this;
|
|
40190
42109
|
if (value == null) return this;
|
|
40191
42110
|
if (value) this.lastWrite = value;
|
|
40192
|
-
|
|
42111
|
+
fs4.appendFileSync(this._path, value, {
|
|
40193
42112
|
encoding: this._encoding
|
|
40194
42113
|
});
|
|
40195
42114
|
return this;
|
|
@@ -40238,6 +42157,233 @@ var BitmarkFileGenerator = class {
|
|
|
40238
42157
|
this.generator.generateSync(_ast);
|
|
40239
42158
|
}
|
|
40240
42159
|
};
|
|
42160
|
+
|
|
42161
|
+
// src/generator/html/HtmlTableGenerator.ts
|
|
42162
|
+
var MARK_TO_TAG = {
|
|
42163
|
+
bold: "b",
|
|
42164
|
+
italic: "i",
|
|
42165
|
+
superscript: "sup",
|
|
42166
|
+
subscript: "sub",
|
|
42167
|
+
strike: "s",
|
|
42168
|
+
del: "del",
|
|
42169
|
+
ins: "ins",
|
|
42170
|
+
underline: "u",
|
|
42171
|
+
highlight: "mark",
|
|
42172
|
+
code: "code"
|
|
42173
|
+
};
|
|
42174
|
+
var HtmlTableGenerator = class {
|
|
42175
|
+
indentUnit = " ";
|
|
42176
|
+
/**
|
|
42177
|
+
* Generate HTML table fragments from a string or JSON value.
|
|
42178
|
+
* @returns the concatenated `<table>` fragments, blank-line separated (empty string if none).
|
|
42179
|
+
*/
|
|
42180
|
+
generate(input) {
|
|
42181
|
+
let data = input;
|
|
42182
|
+
if (typeof data === "string") {
|
|
42183
|
+
try {
|
|
42184
|
+
data = JSON.parse(data);
|
|
42185
|
+
} catch {
|
|
42186
|
+
return "";
|
|
42187
|
+
}
|
|
42188
|
+
}
|
|
42189
|
+
const bits = this.collectBits(data);
|
|
42190
|
+
const fragments = [];
|
|
42191
|
+
for (const bit of bits) {
|
|
42192
|
+
const html = this.renderTableBit(bit);
|
|
42193
|
+
if (html) fragments.push(html);
|
|
42194
|
+
}
|
|
42195
|
+
return fragments.join("\n\n");
|
|
42196
|
+
}
|
|
42197
|
+
// --- bit collection -------------------------------------------------------
|
|
42198
|
+
collectBits(data) {
|
|
42199
|
+
const out = [];
|
|
42200
|
+
const visit = (value) => {
|
|
42201
|
+
if (value == null || typeof value !== "object") return;
|
|
42202
|
+
if (Array.isArray(value)) {
|
|
42203
|
+
value.forEach(visit);
|
|
42204
|
+
return;
|
|
42205
|
+
}
|
|
42206
|
+
const obj = value;
|
|
42207
|
+
if ("bit" in obj && obj.bit && typeof obj.bit === "object") {
|
|
42208
|
+
out.push(obj.bit);
|
|
42209
|
+
} else if ("type" in obj) {
|
|
42210
|
+
out.push(obj);
|
|
42211
|
+
}
|
|
42212
|
+
};
|
|
42213
|
+
visit(data);
|
|
42214
|
+
return out;
|
|
42215
|
+
}
|
|
42216
|
+
// --- table rendering ------------------------------------------------------
|
|
42217
|
+
renderTableBit(bit) {
|
|
42218
|
+
const table = bit.table;
|
|
42219
|
+
if (!table) return "";
|
|
42220
|
+
if (bit.type !== "table" && bit.type !== "table-extended") return "";
|
|
42221
|
+
const extended = this.toExtended(table);
|
|
42222
|
+
const lines = [];
|
|
42223
|
+
lines.push("<table>");
|
|
42224
|
+
if (bit.caption != null) {
|
|
42225
|
+
const caption = this.cellContentToHtml(this.asTextAst(bit.caption));
|
|
42226
|
+
if (caption) lines.push(`${this.indentUnit}<caption>${caption}</caption>`);
|
|
42227
|
+
}
|
|
42228
|
+
this.renderSection(lines, "thead", extended.header, "header");
|
|
42229
|
+
this.renderSection(lines, "tbody", extended.body, "body");
|
|
42230
|
+
this.renderSection(lines, "tfoot", extended.footer, "footer");
|
|
42231
|
+
lines.push("</table>");
|
|
42232
|
+
return lines.join("\n");
|
|
42233
|
+
}
|
|
42234
|
+
toExtended(table) {
|
|
42235
|
+
const t = table;
|
|
42236
|
+
if (t.header || t.body || t.footer) return table;
|
|
42237
|
+
return convertBasicToExtendedTableFormat(table);
|
|
42238
|
+
}
|
|
42239
|
+
renderSection(lines, tag, section, key) {
|
|
42240
|
+
if (!section || !section.rows || section.rows.length === 0) return;
|
|
42241
|
+
lines.push(`${this.indentUnit}<${tag}>`);
|
|
42242
|
+
for (const row of section.rows) {
|
|
42243
|
+
lines.push(`${this.indentUnit.repeat(2)}<tr>`);
|
|
42244
|
+
for (const cell of row.cells ?? []) {
|
|
42245
|
+
lines.push(`${this.indentUnit.repeat(3)}${this.renderCell(cell, key)}`);
|
|
42246
|
+
}
|
|
42247
|
+
lines.push(`${this.indentUnit.repeat(2)}</tr>`);
|
|
42248
|
+
}
|
|
42249
|
+
lines.push(`${this.indentUnit}</${tag}>`);
|
|
42250
|
+
}
|
|
42251
|
+
renderCell(cell, _key) {
|
|
42252
|
+
const tag = cell.title === true ? "th" : "td";
|
|
42253
|
+
const attrs = [];
|
|
42254
|
+
if (cell.rowspan && cell.rowspan > 1) attrs.push(`rowspan="${cell.rowspan}"`);
|
|
42255
|
+
if (cell.colspan && cell.colspan > 1) attrs.push(`colspan="${cell.colspan}"`);
|
|
42256
|
+
if (cell.scope) attrs.push(`scope="${cell.scope}"`);
|
|
42257
|
+
if (cell.colwidth && cell.colwidth > 0) attrs.push(`width="${cell.colwidth}"`);
|
|
42258
|
+
const open = attrs.length > 0 ? `<${tag} ${attrs.join(" ")}>` : `<${tag}>`;
|
|
42259
|
+
return `${open}${this.cellContentToHtml(this.asTextAst(cell.content))}</${tag}>`;
|
|
42260
|
+
}
|
|
42261
|
+
// --- cell content (TextAst -> HTML) ---------------------------------------
|
|
42262
|
+
asTextAst(content) {
|
|
42263
|
+
if (content == null) return [];
|
|
42264
|
+
if (typeof content === "string") return [{ type: "text", text: content }];
|
|
42265
|
+
return content;
|
|
42266
|
+
}
|
|
42267
|
+
cellContentToHtml(blocks) {
|
|
42268
|
+
if (blocks.length === 0) return "";
|
|
42269
|
+
if (blocks.length === 1 && blocks[0].type === "paragraph") {
|
|
42270
|
+
return this.inlineToHtml(blocks[0].content ?? []);
|
|
42271
|
+
}
|
|
42272
|
+
return blocks.map((b) => this.blockToHtml(b)).join("");
|
|
42273
|
+
}
|
|
42274
|
+
blockToHtml(node) {
|
|
42275
|
+
switch (node.type) {
|
|
42276
|
+
case "paragraph":
|
|
42277
|
+
return `<p>${this.inlineToHtml(node.content ?? [])}</p>`;
|
|
42278
|
+
case "bulletList":
|
|
42279
|
+
case "noBulletList":
|
|
42280
|
+
case "orderedList":
|
|
42281
|
+
case "orderedListRoman":
|
|
42282
|
+
case "orderedListRomanLower":
|
|
42283
|
+
case "letteredList":
|
|
42284
|
+
case "letteredListLower":
|
|
42285
|
+
case "taskList":
|
|
42286
|
+
return this.listToHtml(node);
|
|
42287
|
+
case "image":
|
|
42288
|
+
case "imageInline":
|
|
42289
|
+
return this.imageToHtml(node);
|
|
42290
|
+
default:
|
|
42291
|
+
return this.inlineToHtml([node]);
|
|
42292
|
+
}
|
|
42293
|
+
}
|
|
42294
|
+
inlineToHtml(nodes) {
|
|
42295
|
+
return nodes.map((n) => this.inlineNodeToHtml(n)).join("");
|
|
42296
|
+
}
|
|
42297
|
+
inlineNodeToHtml(node) {
|
|
42298
|
+
if (node.type === "hardBreak") return "<br>";
|
|
42299
|
+
if (node.type === "image" || node.type === "imageInline") return this.imageToHtml(node);
|
|
42300
|
+
if (node.type === "text") {
|
|
42301
|
+
return this.wrapMarks(this.escapeText(node.text ?? ""), node.marks);
|
|
42302
|
+
}
|
|
42303
|
+
if (node.content) return this.inlineToHtml(node.content);
|
|
42304
|
+
return this.escapeText(node.text ?? "");
|
|
42305
|
+
}
|
|
42306
|
+
wrapMarks(text, marks) {
|
|
42307
|
+
if (!marks || marks.length === 0) return text;
|
|
42308
|
+
let open = "";
|
|
42309
|
+
let close = "";
|
|
42310
|
+
for (const mark of marks) {
|
|
42311
|
+
if (mark.type === "link") {
|
|
42312
|
+
const href = mark.attrs?.href ?? "";
|
|
42313
|
+
open += `<a href="${this.escapeAttr(href)}">`;
|
|
42314
|
+
close = `</a>${close}`;
|
|
42315
|
+
} else {
|
|
42316
|
+
const tag = MARK_TO_TAG[mark.type];
|
|
42317
|
+
if (tag) {
|
|
42318
|
+
open += `<${tag}>`;
|
|
42319
|
+
close = `</${tag}>${close}`;
|
|
42320
|
+
}
|
|
42321
|
+
}
|
|
42322
|
+
}
|
|
42323
|
+
return `${open}${text}${close}`;
|
|
42324
|
+
}
|
|
42325
|
+
// --- lists ----------------------------------------------------------------
|
|
42326
|
+
listToHtml(node) {
|
|
42327
|
+
const { tag, attrs } = this.listTag(node);
|
|
42328
|
+
const items = (node.content ?? []).map((item) => this.listItemToHtml(item, node.type === "taskList")).join("");
|
|
42329
|
+
return `<${tag}${attrs}>${items}</${tag}>`;
|
|
42330
|
+
}
|
|
42331
|
+
listTag(node) {
|
|
42332
|
+
const start = node.attrs?.start;
|
|
42333
|
+
const startAttr = typeof start === "number" && start !== 1 ? ` start="${start}"` : "";
|
|
42334
|
+
switch (node.type) {
|
|
42335
|
+
case "orderedList":
|
|
42336
|
+
return { tag: "ol", attrs: startAttr };
|
|
42337
|
+
case "orderedListRoman":
|
|
42338
|
+
return { tag: "ol", attrs: ` type="I"${startAttr}` };
|
|
42339
|
+
case "orderedListRomanLower":
|
|
42340
|
+
return { tag: "ol", attrs: ` type="i"${startAttr}` };
|
|
42341
|
+
case "letteredList":
|
|
42342
|
+
return { tag: "ol", attrs: ` type="A"${startAttr}` };
|
|
42343
|
+
case "letteredListLower":
|
|
42344
|
+
return { tag: "ol", attrs: ` type="a"${startAttr}` };
|
|
42345
|
+
case "noBulletList":
|
|
42346
|
+
return { tag: "ul", attrs: ' style="list-style:none"' };
|
|
42347
|
+
case "taskList":
|
|
42348
|
+
case "bulletList":
|
|
42349
|
+
default:
|
|
42350
|
+
return { tag: "ul", attrs: "" };
|
|
42351
|
+
}
|
|
42352
|
+
}
|
|
42353
|
+
listItemToHtml(item, task) {
|
|
42354
|
+
const inner = (item.content ?? []).map((b) => {
|
|
42355
|
+
if (b.type === "paragraph") return this.inlineToHtml(b.content ?? []);
|
|
42356
|
+
return this.blockToHtml(b);
|
|
42357
|
+
}).join("");
|
|
42358
|
+
if (task) {
|
|
42359
|
+
const checked = item.attrs?.checked ? " checked" : "";
|
|
42360
|
+
return `<li><input type="checkbox"${checked}>${inner}</li>`;
|
|
42361
|
+
}
|
|
42362
|
+
return `<li>${inner}</li>`;
|
|
42363
|
+
}
|
|
42364
|
+
// --- images ---------------------------------------------------------------
|
|
42365
|
+
imageToHtml(node) {
|
|
42366
|
+
const attrs = node.attrs ?? {};
|
|
42367
|
+
const parts = [];
|
|
42368
|
+
const src = attrs.src;
|
|
42369
|
+
parts.push(`src="${this.escapeAttr(typeof src === "string" ? src : "")}"`);
|
|
42370
|
+
if (typeof attrs.alt === "string") parts.push(`alt="${this.escapeAttr(attrs.alt)}"`);
|
|
42371
|
+
if (typeof attrs.title === "string") parts.push(`title="${this.escapeAttr(attrs.title)}"`);
|
|
42372
|
+
if (attrs.width != null) parts.push(`width="${attrs.width}"`);
|
|
42373
|
+
if (attrs.height != null) parts.push(`height="${attrs.height}"`);
|
|
42374
|
+
if (typeof attrs.class === "string" && attrs.class) {
|
|
42375
|
+
parts.push(`class="${this.escapeAttr(attrs.class)}"`);
|
|
42376
|
+
}
|
|
42377
|
+
return `<img ${parts.join(" ")}>`;
|
|
42378
|
+
}
|
|
42379
|
+
// --- escaping -------------------------------------------------------------
|
|
42380
|
+
escapeText(text) {
|
|
42381
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
42382
|
+
}
|
|
42383
|
+
escapeAttr(text) {
|
|
42384
|
+
return this.escapeText(text).replace(/"/g, """);
|
|
42385
|
+
}
|
|
42386
|
+
};
|
|
40241
42387
|
var JsonFileGenerator = class {
|
|
40242
42388
|
generator;
|
|
40243
42389
|
async;
|
|
@@ -40574,20 +42720,20 @@ var ConfigBuilder = class {
|
|
|
40574
42720
|
const outputFolderBits = path3.join(outputFolder, "bits");
|
|
40575
42721
|
const outputFolderGroups = path3.join(outputFolder, "partials");
|
|
40576
42722
|
const outputFolderCards = path3.join(outputFolder, "cards");
|
|
40577
|
-
|
|
40578
|
-
|
|
40579
|
-
|
|
40580
|
-
const bitsFiles =
|
|
42723
|
+
fs4.ensureDirSync(outputFolderBits);
|
|
42724
|
+
fs4.ensureDirSync(outputFolderGroups);
|
|
42725
|
+
fs4.ensureDirSync(outputFolderCards);
|
|
42726
|
+
const bitsFiles = fs4.readdirSync(outputFolderBits).filter((f) => f.endsWith(".jsonc"));
|
|
40581
42727
|
for (const file of bitsFiles) {
|
|
40582
|
-
|
|
42728
|
+
fs4.removeSync(path3.join(outputFolderBits, file));
|
|
40583
42729
|
}
|
|
40584
|
-
const partialsFiles =
|
|
42730
|
+
const partialsFiles = fs4.readdirSync(outputFolderGroups).filter((f) => f.endsWith(".jsonc"));
|
|
40585
42731
|
for (const file of partialsFiles) {
|
|
40586
|
-
|
|
42732
|
+
fs4.removeSync(path3.join(outputFolderGroups, file));
|
|
40587
42733
|
}
|
|
40588
|
-
const cardsFiles =
|
|
42734
|
+
const cardsFiles = fs4.readdirSync(outputFolderCards).filter((f) => f.endsWith(".jsonc"));
|
|
40589
42735
|
for (const file of cardsFiles) {
|
|
40590
|
-
|
|
42736
|
+
fs4.removeSync(path3.join(outputFolderCards, file));
|
|
40591
42737
|
}
|
|
40592
42738
|
const fileWrites = [];
|
|
40593
42739
|
const tagEntriesTypeOrder = [
|
|
@@ -40729,6 +42875,13 @@ var ConfigBuilder = class {
|
|
|
40729
42875
|
const cardSetConfig = CARDSETS[cardSetKey];
|
|
40730
42876
|
if (!cardSetConfig) return void 0;
|
|
40731
42877
|
const normalizedKey = normalizeCardKey(cardSetKey);
|
|
42878
|
+
const variantTagEntriesTypeOrder = [
|
|
42879
|
+
BitTagConfigKeyType.group,
|
|
42880
|
+
BitTagConfigKeyType.tag,
|
|
42881
|
+
BitTagConfigKeyType.property,
|
|
42882
|
+
BitTagConfigKeyType.resource,
|
|
42883
|
+
BitTagConfigKeyType.unknown
|
|
42884
|
+
];
|
|
40732
42885
|
const sides = cardSetConfig.sides.map((side, sideIdx) => {
|
|
40733
42886
|
const sidePath = `cardSets.${normalizedKey} / sides.${side.name ?? `[${sideIdx}]`}`;
|
|
40734
42887
|
const variants = side.variants.map((variant, variantIdx) => {
|
|
@@ -40739,7 +42892,7 @@ var ConfigBuilder = class {
|
|
|
40739
42892
|
const tagB = b[1];
|
|
40740
42893
|
const typeA = typeFromConfigKey(tagA.key);
|
|
40741
42894
|
const typeB = typeFromConfigKey(tagB.key);
|
|
40742
|
-
const typeOrder =
|
|
42895
|
+
const typeOrder = variantTagEntriesTypeOrder.indexOf(typeA) - variantTagEntriesTypeOrder.indexOf(typeB);
|
|
40743
42896
|
return typeOrder;
|
|
40744
42897
|
});
|
|
40745
42898
|
for (const [, variantTag] of variantTagEntries) {
|
|
@@ -40779,6 +42932,10 @@ var ConfigBuilder = class {
|
|
|
40779
42932
|
name: sectionName,
|
|
40780
42933
|
...section.isDefault ? { isDefault: true } : {},
|
|
40781
42934
|
...cardJsonKeyField(section, "jsonKey", "exportJsonKey", sectionPath),
|
|
42935
|
+
// PLAN-085: per-section cardinality. Emit only when non-default
|
|
42936
|
+
// (treat `0` / undefined as "unbounded — omit").
|
|
42937
|
+
...section.minCount != null && section.minCount !== 0 ? { min: section.minCount } : {},
|
|
42938
|
+
...section.maxCount != null && section.maxCount !== 0 ? { max: section.maxCount } : {},
|
|
40782
42939
|
sides: cardSides
|
|
40783
42940
|
};
|
|
40784
42941
|
});
|
|
@@ -40848,10 +43005,20 @@ var ConfigBuilder = class {
|
|
|
40848
43005
|
return typeOrder;
|
|
40849
43006
|
});
|
|
40850
43007
|
const isInheritedFrom = bitGroupConfigKeys.has(b.bitType);
|
|
43008
|
+
const indexByKey = /* @__PURE__ */ new Map();
|
|
43009
|
+
const pushOrReplace = (entry) => {
|
|
43010
|
+
const existing = indexByKey.get(entry.key);
|
|
43011
|
+
if (existing !== void 0) {
|
|
43012
|
+
tags2[existing] = entry;
|
|
43013
|
+
} else {
|
|
43014
|
+
indexByKey.set(entry.key, tags2.length);
|
|
43015
|
+
tags2.push(entry);
|
|
43016
|
+
}
|
|
43017
|
+
};
|
|
40851
43018
|
if (isInheritedFrom) {
|
|
40852
43019
|
const resolvedGroups = resolveGroupReferences(`group-${b.bitType}`);
|
|
40853
43020
|
for (const ref of resolvedGroups) {
|
|
40854
|
-
|
|
43021
|
+
pushOrReplace({
|
|
40855
43022
|
type: "group",
|
|
40856
43023
|
key: ref.key,
|
|
40857
43024
|
...ref.hasExportJsonKey ? { jsonKey: ref.exportJsonKey } : {},
|
|
@@ -40864,7 +43031,7 @@ var ConfigBuilder = class {
|
|
|
40864
43031
|
if (b.baseBitType) {
|
|
40865
43032
|
const resolvedGroups = resolveGroupReferences(`group-${b.baseBitType}`);
|
|
40866
43033
|
for (const ref of resolvedGroups) {
|
|
40867
|
-
|
|
43034
|
+
pushOrReplace({
|
|
40868
43035
|
type: "group",
|
|
40869
43036
|
key: ref.key,
|
|
40870
43037
|
...ref.hasExportJsonKey ? { jsonKey: ref.exportJsonKey } : {},
|
|
@@ -40875,7 +43042,9 @@ var ConfigBuilder = class {
|
|
|
40875
43042
|
}
|
|
40876
43043
|
}
|
|
40877
43044
|
for (const [_tagKey, tag] of tagEntries) {
|
|
40878
|
-
|
|
43045
|
+
for (const entry of processTagEntries(tag, [])) {
|
|
43046
|
+
pushOrReplace(entry);
|
|
43047
|
+
}
|
|
40879
43048
|
}
|
|
40880
43049
|
}
|
|
40881
43050
|
let cardRef;
|
|
@@ -40909,7 +43078,7 @@ var ConfigBuilder = class {
|
|
|
40909
43078
|
};
|
|
40910
43079
|
const output = path3.join(outputFolderBits, `${b.bitType}.jsonc`);
|
|
40911
43080
|
const str = JSON.stringify(bitJson, null, 2);
|
|
40912
|
-
fileWrites.push(
|
|
43081
|
+
fileWrites.push(fs4.writeFile(output, str));
|
|
40913
43082
|
}
|
|
40914
43083
|
const writeGroupConfigs = (groupConfigs2) => {
|
|
40915
43084
|
for (const g of groupConfigs2) {
|
|
@@ -40968,7 +43137,7 @@ var ConfigBuilder = class {
|
|
|
40968
43137
|
};
|
|
40969
43138
|
const output = path3.join(outputFolderGroups, `${groupKey}.jsonc`);
|
|
40970
43139
|
const str = JSON.stringify(bitJson, null, 2);
|
|
40971
|
-
|
|
43140
|
+
fs4.writeFileSync(output, str);
|
|
40972
43141
|
}
|
|
40973
43142
|
};
|
|
40974
43143
|
writeGroupConfigs(groupConfigs);
|
|
@@ -40978,7 +43147,7 @@ var ConfigBuilder = class {
|
|
|
40978
43147
|
if (!cardJson) continue;
|
|
40979
43148
|
const output = path3.join(outputFolderCards, `${cardJson.name}.jsonc`);
|
|
40980
43149
|
const str = JSON.stringify(cardJson, null, 2);
|
|
40981
|
-
fileWrites.push(
|
|
43150
|
+
fileWrites.push(fs4.writeFile(output, str));
|
|
40982
43151
|
}
|
|
40983
43152
|
};
|
|
40984
43153
|
const writeBitsAsGroupConfigs = (bitsAsGroupConfigs) => {
|
|
@@ -41027,7 +43196,7 @@ var ConfigBuilder = class {
|
|
|
41027
43196
|
};
|
|
41028
43197
|
const output = path3.join(outputFolderGroups, `${groupKey}.jsonc`);
|
|
41029
43198
|
const str = JSON.stringify(bitJson, null, 2);
|
|
41030
|
-
|
|
43199
|
+
fs4.writeFileSync(output, str);
|
|
41031
43200
|
}
|
|
41032
43201
|
};
|
|
41033
43202
|
writeBitsAsGroupConfigs(bitGroupConfigs);
|
|
@@ -41066,15 +43235,15 @@ var ConfigBuilder = class {
|
|
|
41066
43235
|
const outputFolderGroups = path3.join(outputFolder, "partials");
|
|
41067
43236
|
const outputFolderCards = path3.join(outputFolder, "cards");
|
|
41068
43237
|
const availableGroups = /* @__PURE__ */ new Set();
|
|
41069
|
-
if (
|
|
41070
|
-
const groupFiles =
|
|
43238
|
+
if (fs4.existsSync(outputFolderGroups)) {
|
|
43239
|
+
const groupFiles = fs4.readdirSync(outputFolderGroups).filter((f) => f.endsWith(".jsonc"));
|
|
41071
43240
|
for (const file of groupFiles) {
|
|
41072
43241
|
const groupName = file.replace(".jsonc", "");
|
|
41073
43242
|
availableGroups.add(groupName);
|
|
41074
43243
|
}
|
|
41075
43244
|
}
|
|
41076
43245
|
const availableCards = /* @__PURE__ */ new Set();
|
|
41077
|
-
const cardFiles =
|
|
43246
|
+
const cardFiles = fs4.existsSync(outputFolderCards) ? fs4.readdirSync(outputFolderCards).filter((f) => f.endsWith(".jsonc")) : [];
|
|
41078
43247
|
for (const file of cardFiles) {
|
|
41079
43248
|
const cardName = file.replace(".jsonc", "");
|
|
41080
43249
|
availableCards.add(cardName);
|
|
@@ -41093,12 +43262,12 @@ var ConfigBuilder = class {
|
|
|
41093
43262
|
}
|
|
41094
43263
|
}
|
|
41095
43264
|
};
|
|
41096
|
-
if (
|
|
41097
|
-
const bitFiles =
|
|
43265
|
+
if (fs4.existsSync(outputFolderBits)) {
|
|
43266
|
+
const bitFiles = fs4.readdirSync(outputFolderBits).filter((f) => f.endsWith(".jsonc"));
|
|
41098
43267
|
for (const file of bitFiles) {
|
|
41099
43268
|
const filePath = path3.join(outputFolderBits, file);
|
|
41100
43269
|
try {
|
|
41101
|
-
const content =
|
|
43270
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
41102
43271
|
const config = JSON.parse(content);
|
|
41103
43272
|
if (config.tags && Array.isArray(config.tags)) {
|
|
41104
43273
|
checkTags(config.tags, `bits/${file}`, 15);
|
|
@@ -41129,12 +43298,12 @@ var ConfigBuilder = class {
|
|
|
41129
43298
|
}
|
|
41130
43299
|
}
|
|
41131
43300
|
}
|
|
41132
|
-
if (
|
|
41133
|
-
const groupFiles =
|
|
43301
|
+
if (fs4.existsSync(outputFolderGroups)) {
|
|
43302
|
+
const groupFiles = fs4.readdirSync(outputFolderGroups).filter((f) => f.endsWith(".jsonc"));
|
|
41134
43303
|
for (const file of groupFiles) {
|
|
41135
43304
|
const filePath = path3.join(outputFolderGroups, file);
|
|
41136
43305
|
try {
|
|
41137
|
-
const content =
|
|
43306
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
41138
43307
|
const config = JSON.parse(content);
|
|
41139
43308
|
if (config.tags && Array.isArray(config.tags)) {
|
|
41140
43309
|
checkTags(config.tags, `partials/${file}`, 15);
|
|
@@ -41148,7 +43317,7 @@ var ConfigBuilder = class {
|
|
|
41148
43317
|
for (const file of cardFiles) {
|
|
41149
43318
|
const filePath = path3.join(outputFolderCards, file);
|
|
41150
43319
|
try {
|
|
41151
|
-
const content =
|
|
43320
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
41152
43321
|
const config = JSON.parse(content);
|
|
41153
43322
|
const expectedKey = file.replace(".jsonc", "");
|
|
41154
43323
|
if (config.key && config.key !== expectedKey) {
|
|
@@ -41184,6 +43353,497 @@ var ConfigBuilder = class {
|
|
|
41184
43353
|
}
|
|
41185
43354
|
};
|
|
41186
43355
|
|
|
43356
|
+
// src/parser/html/HtmlDom.ts
|
|
43357
|
+
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
43358
|
+
"area",
|
|
43359
|
+
"base",
|
|
43360
|
+
"br",
|
|
43361
|
+
"col",
|
|
43362
|
+
"embed",
|
|
43363
|
+
"hr",
|
|
43364
|
+
"img",
|
|
43365
|
+
"input",
|
|
43366
|
+
"link",
|
|
43367
|
+
"meta",
|
|
43368
|
+
"param",
|
|
43369
|
+
"source",
|
|
43370
|
+
"track",
|
|
43371
|
+
"wbr"
|
|
43372
|
+
]);
|
|
43373
|
+
var IMPLIED_CLOSE = {
|
|
43374
|
+
li: /* @__PURE__ */ new Set(["li"]),
|
|
43375
|
+
td: /* @__PURE__ */ new Set(["td", "th"]),
|
|
43376
|
+
th: /* @__PURE__ */ new Set(["td", "th"]),
|
|
43377
|
+
tr: /* @__PURE__ */ new Set(["td", "th", "tr"]),
|
|
43378
|
+
thead: /* @__PURE__ */ new Set(["td", "th", "tr", "thead", "tbody", "tfoot"]),
|
|
43379
|
+
tbody: /* @__PURE__ */ new Set(["td", "th", "tr", "thead", "tbody", "tfoot"]),
|
|
43380
|
+
tfoot: /* @__PURE__ */ new Set(["td", "th", "tr", "thead", "tbody", "tfoot"]),
|
|
43381
|
+
p: /* @__PURE__ */ new Set(["p"]),
|
|
43382
|
+
option: /* @__PURE__ */ new Set(["option"])
|
|
43383
|
+
};
|
|
43384
|
+
var TAG_RE = /<(\/)?([a-zA-Z][a-zA-Z0-9-]*)((?:\s+(?:"[^"]*"|'[^']*'|[^<>"'])*)?)(\/)?>/g;
|
|
43385
|
+
var ATTR_RE = /([a-zA-Z_:][a-zA-Z0-9:._-]*)(?:\s*=\s*("[^"]*"|'[^']*'|[^\s"'>]+))?/g;
|
|
43386
|
+
function parseAttrs(raw) {
|
|
43387
|
+
const attrs = {};
|
|
43388
|
+
if (!raw) return attrs;
|
|
43389
|
+
ATTR_RE.lastIndex = 0;
|
|
43390
|
+
let m;
|
|
43391
|
+
while ((m = ATTR_RE.exec(raw)) !== null) {
|
|
43392
|
+
const name = m[1].toLowerCase();
|
|
43393
|
+
let value = m[2] ?? "";
|
|
43394
|
+
if (value.length >= 2 && (value[0] === '"' || value[0] === "'")) {
|
|
43395
|
+
value = value.slice(1, -1);
|
|
43396
|
+
}
|
|
43397
|
+
attrs[name] = value;
|
|
43398
|
+
}
|
|
43399
|
+
return attrs;
|
|
43400
|
+
}
|
|
43401
|
+
function stripNonContent(html) {
|
|
43402
|
+
return html.replace(/<!--[\s\S]*?-->/g, "").replace(/<!\[CDATA\[[\s\S]*?\]\]>/g, "").replace(/<!doctype[^>]*>/gi, "").replace(/<script\b[\s\S]*?<\/script>/gi, "").replace(/<style\b[\s\S]*?<\/style>/gi, "");
|
|
43403
|
+
}
|
|
43404
|
+
function tokenize(html) {
|
|
43405
|
+
const tokens = [];
|
|
43406
|
+
const clean = stripNonContent(html);
|
|
43407
|
+
TAG_RE.lastIndex = 0;
|
|
43408
|
+
let lastIndex = 0;
|
|
43409
|
+
let m;
|
|
43410
|
+
while ((m = TAG_RE.exec(clean)) !== null) {
|
|
43411
|
+
if (m.index > lastIndex) {
|
|
43412
|
+
tokens.push({ kind: "text", text: clean.slice(lastIndex, m.index) });
|
|
43413
|
+
}
|
|
43414
|
+
lastIndex = TAG_RE.lastIndex;
|
|
43415
|
+
const isClose = m[1] === "/";
|
|
43416
|
+
const name = m[2].toLowerCase();
|
|
43417
|
+
if (isClose) {
|
|
43418
|
+
tokens.push({ kind: "close", name });
|
|
43419
|
+
} else {
|
|
43420
|
+
tokens.push({
|
|
43421
|
+
kind: "open",
|
|
43422
|
+
name,
|
|
43423
|
+
attrs: parseAttrs(m[3]),
|
|
43424
|
+
selfClose: m[4] === "/"
|
|
43425
|
+
});
|
|
43426
|
+
}
|
|
43427
|
+
}
|
|
43428
|
+
if (lastIndex < clean.length) {
|
|
43429
|
+
tokens.push({ kind: "text", text: clean.slice(lastIndex) });
|
|
43430
|
+
}
|
|
43431
|
+
return tokens;
|
|
43432
|
+
}
|
|
43433
|
+
function parseHtml(html) {
|
|
43434
|
+
const root = { type: "element", name: "#root", attrs: {}, children: [] };
|
|
43435
|
+
const stack = [root];
|
|
43436
|
+
const top = () => stack[stack.length - 1];
|
|
43437
|
+
for (const token of tokenize(html)) {
|
|
43438
|
+
if (token.kind === "text") {
|
|
43439
|
+
if (token.text) top().children.push({ type: "text", value: token.text });
|
|
43440
|
+
continue;
|
|
43441
|
+
}
|
|
43442
|
+
if (token.kind === "open") {
|
|
43443
|
+
const name = token.name;
|
|
43444
|
+
const closes = IMPLIED_CLOSE[name];
|
|
43445
|
+
if (closes) {
|
|
43446
|
+
while (stack.length > 1 && closes.has(top().name)) stack.pop();
|
|
43447
|
+
}
|
|
43448
|
+
const el = {
|
|
43449
|
+
type: "element",
|
|
43450
|
+
name,
|
|
43451
|
+
attrs: token.attrs ?? {},
|
|
43452
|
+
children: []
|
|
43453
|
+
};
|
|
43454
|
+
top().children.push(el);
|
|
43455
|
+
if (!token.selfClose && !VOID_ELEMENTS.has(name)) {
|
|
43456
|
+
stack.push(el);
|
|
43457
|
+
}
|
|
43458
|
+
continue;
|
|
43459
|
+
}
|
|
43460
|
+
for (let i = stack.length - 1; i > 0; i--) {
|
|
43461
|
+
if (stack[i].name === token.name) {
|
|
43462
|
+
stack.length = i;
|
|
43463
|
+
break;
|
|
43464
|
+
}
|
|
43465
|
+
}
|
|
43466
|
+
}
|
|
43467
|
+
return root.children;
|
|
43468
|
+
}
|
|
43469
|
+
function childElements(el, names) {
|
|
43470
|
+
return el.children.filter((c) => c.type === "element" && names.has(c.name));
|
|
43471
|
+
}
|
|
43472
|
+
|
|
43473
|
+
// src/parser/html/HtmlTableParser.ts
|
|
43474
|
+
var MARK_TAGS = {
|
|
43475
|
+
b: "bold",
|
|
43476
|
+
strong: "bold",
|
|
43477
|
+
i: "italic",
|
|
43478
|
+
em: "italic",
|
|
43479
|
+
sup: "superscript",
|
|
43480
|
+
sub: "subscript",
|
|
43481
|
+
s: "strike",
|
|
43482
|
+
strike: "strike",
|
|
43483
|
+
del: "del",
|
|
43484
|
+
ins: "ins",
|
|
43485
|
+
u: "underline",
|
|
43486
|
+
mark: "highlight",
|
|
43487
|
+
code: "code"
|
|
43488
|
+
};
|
|
43489
|
+
var SECTION_TAGS = /* @__PURE__ */ new Set(["thead", "tbody", "tfoot"]);
|
|
43490
|
+
var ROW_TAGS = /* @__PURE__ */ new Set(["tr"]);
|
|
43491
|
+
var CELL_TAGS = /* @__PURE__ */ new Set(["td", "th"]);
|
|
43492
|
+
var LI_TAGS = /* @__PURE__ */ new Set(["li"]);
|
|
43493
|
+
var VALID_SCOPES = /* @__PURE__ */ new Set(["row", "col", "rowgroup", "colgroup"]);
|
|
43494
|
+
var NAMED_ENTITIES = {
|
|
43495
|
+
amp: "&",
|
|
43496
|
+
lt: "<",
|
|
43497
|
+
gt: ">",
|
|
43498
|
+
quot: '"',
|
|
43499
|
+
apos: "'",
|
|
43500
|
+
nbsp: "\xA0"
|
|
43501
|
+
};
|
|
43502
|
+
function decodeEntities(text) {
|
|
43503
|
+
return text.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*);/g, (whole, body) => {
|
|
43504
|
+
if (body[0] === "#") {
|
|
43505
|
+
const code = body[1] === "x" || body[1] === "X" ? Number.parseInt(body.slice(2), 16) : Number.parseInt(body.slice(1), 10);
|
|
43506
|
+
if (Number.isFinite(code) && code >= 0 && code <= 1114111) {
|
|
43507
|
+
try {
|
|
43508
|
+
return String.fromCodePoint(code);
|
|
43509
|
+
} catch {
|
|
43510
|
+
return whole;
|
|
43511
|
+
}
|
|
43512
|
+
}
|
|
43513
|
+
return whole;
|
|
43514
|
+
}
|
|
43515
|
+
return NAMED_ENTITIES[body] ?? whole;
|
|
43516
|
+
});
|
|
43517
|
+
}
|
|
43518
|
+
function intAttr(value) {
|
|
43519
|
+
if (value == null) return void 0;
|
|
43520
|
+
const n = Number.parseInt(value, 10);
|
|
43521
|
+
return Number.isInteger(n) ? n : void 0;
|
|
43522
|
+
}
|
|
43523
|
+
function isElement(node) {
|
|
43524
|
+
return node.type === "element";
|
|
43525
|
+
}
|
|
43526
|
+
var HtmlTableParser = class {
|
|
43527
|
+
keepUnknownTags = false;
|
|
43528
|
+
/**
|
|
43529
|
+
* Parse an HTML string and return one table bit per top-level `<table>`.
|
|
43530
|
+
*/
|
|
43531
|
+
parse(html, options) {
|
|
43532
|
+
this.keepUnknownTags = options?.keepUnknownTags ?? false;
|
|
43533
|
+
const tableFormat = options?.tableFormat ?? "table-extended";
|
|
43534
|
+
const onWarning = options?.onWarning;
|
|
43535
|
+
const tables = this.collectTopLevelTables(parseHtml(html));
|
|
43536
|
+
const bits = [];
|
|
43537
|
+
for (const table of tables) {
|
|
43538
|
+
const bit = this.tableToBit(table, tableFormat, onWarning);
|
|
43539
|
+
if (bit) bits.push(bit);
|
|
43540
|
+
}
|
|
43541
|
+
return bits;
|
|
43542
|
+
}
|
|
43543
|
+
// --- table extraction -----------------------------------------------------
|
|
43544
|
+
/** Collect every `<table>` element, NOT descending into nested tables. */
|
|
43545
|
+
collectTopLevelTables(nodes) {
|
|
43546
|
+
const tables = [];
|
|
43547
|
+
const visit = (node) => {
|
|
43548
|
+
if (!isElement(node)) return;
|
|
43549
|
+
if (node.name === "table") {
|
|
43550
|
+
tables.push(node);
|
|
43551
|
+
return;
|
|
43552
|
+
}
|
|
43553
|
+
node.children.forEach(visit);
|
|
43554
|
+
};
|
|
43555
|
+
nodes.forEach(visit);
|
|
43556
|
+
return tables;
|
|
43557
|
+
}
|
|
43558
|
+
tableToBit(table, tableFormat, onWarning) {
|
|
43559
|
+
const header = [];
|
|
43560
|
+
const body = [];
|
|
43561
|
+
const footer = [];
|
|
43562
|
+
let caption;
|
|
43563
|
+
for (const child of table.children) {
|
|
43564
|
+
if (!isElement(child)) continue;
|
|
43565
|
+
if (child.name === "caption") {
|
|
43566
|
+
const capContent = this.inlineNodes(child.children, []);
|
|
43567
|
+
const trimmed = this.trimInline(capContent);
|
|
43568
|
+
if (trimmed.length > 0) caption = trimmed;
|
|
43569
|
+
} else if (SECTION_TAGS.has(child.name)) {
|
|
43570
|
+
const target = child.name === "thead" ? header : child.name === "tfoot" ? footer : body;
|
|
43571
|
+
for (const tr of childElements(child, ROW_TAGS)) {
|
|
43572
|
+
target.push(this.rowToCells(tr));
|
|
43573
|
+
}
|
|
43574
|
+
} else if (ROW_TAGS.has(child.name)) {
|
|
43575
|
+
body.push(this.rowToCells(child));
|
|
43576
|
+
}
|
|
43577
|
+
}
|
|
43578
|
+
if (header.length === 0 && body.length === 0 && footer.length === 0) {
|
|
43579
|
+
return void 0;
|
|
43580
|
+
}
|
|
43581
|
+
const tableExtended = {};
|
|
43582
|
+
if (header.length > 0) tableExtended.header = { rows: header };
|
|
43583
|
+
if (body.length > 0) tableExtended.body = { rows: body };
|
|
43584
|
+
if (footer.length > 0) tableExtended.footer = { rows: footer };
|
|
43585
|
+
let tableData = tableExtended;
|
|
43586
|
+
if (tableFormat === "table") {
|
|
43587
|
+
if (this.isLossyAsBasic(tableExtended)) {
|
|
43588
|
+
onWarning?.(
|
|
43589
|
+
"Converting an HTML table to [.table] loses rowspan/colspan/scope and multi-section structure; use --tableFormat table-extended to preserve them."
|
|
43590
|
+
);
|
|
43591
|
+
}
|
|
43592
|
+
tableData = convertExtendedToBasicTableFormat(tableExtended);
|
|
43593
|
+
}
|
|
43594
|
+
const bit = {
|
|
43595
|
+
type: tableFormat,
|
|
43596
|
+
format: "bitmark++",
|
|
43597
|
+
table: tableData
|
|
43598
|
+
};
|
|
43599
|
+
if (caption) bit.caption = caption;
|
|
43600
|
+
return bit;
|
|
43601
|
+
}
|
|
43602
|
+
rowToCells(tr) {
|
|
43603
|
+
const cells = [];
|
|
43604
|
+
for (const cell of childElements(tr, CELL_TAGS)) {
|
|
43605
|
+
cells.push(this.cellToJson(cell));
|
|
43606
|
+
}
|
|
43607
|
+
return { cells };
|
|
43608
|
+
}
|
|
43609
|
+
cellToJson(cell) {
|
|
43610
|
+
const json = {
|
|
43611
|
+
content: this.blockNodes(cell.children)
|
|
43612
|
+
};
|
|
43613
|
+
if (cell.name === "th") json.title = true;
|
|
43614
|
+
const rowspan = intAttr(cell.attrs.rowspan);
|
|
43615
|
+
if (rowspan != null && rowspan >= 2) json.rowspan = rowspan;
|
|
43616
|
+
const colspan = intAttr(cell.attrs.colspan);
|
|
43617
|
+
if (colspan != null && colspan >= 2) json.colspan = colspan;
|
|
43618
|
+
const scope = cell.attrs.scope?.toLowerCase();
|
|
43619
|
+
if (scope && VALID_SCOPES.has(scope)) {
|
|
43620
|
+
json.scope = scope;
|
|
43621
|
+
}
|
|
43622
|
+
const width = this.widthFromCell(cell);
|
|
43623
|
+
if (width != null) json.colwidth = width;
|
|
43624
|
+
return json;
|
|
43625
|
+
}
|
|
43626
|
+
widthFromCell(cell) {
|
|
43627
|
+
const attrWidth = intAttr(cell.attrs.width);
|
|
43628
|
+
if (attrWidth != null && attrWidth > 0) return attrWidth;
|
|
43629
|
+
const style = cell.attrs.style;
|
|
43630
|
+
if (style) {
|
|
43631
|
+
const m = style.match(/width\s*:\s*(\d+)\s*px/i);
|
|
43632
|
+
if (m) {
|
|
43633
|
+
const n = Number.parseInt(m[1], 10);
|
|
43634
|
+
if (Number.isInteger(n) && n > 0) return n;
|
|
43635
|
+
}
|
|
43636
|
+
}
|
|
43637
|
+
return void 0;
|
|
43638
|
+
}
|
|
43639
|
+
isLossyAsBasic(table) {
|
|
43640
|
+
if (table.footer) return true;
|
|
43641
|
+
if (table.header && table.header.rows.length > 1) return true;
|
|
43642
|
+
const sections = [table.header, table.body, table.footer];
|
|
43643
|
+
for (const section of sections) {
|
|
43644
|
+
if (!section) continue;
|
|
43645
|
+
for (const row of section.rows) {
|
|
43646
|
+
for (const cell of row.cells) {
|
|
43647
|
+
if (cell.rowspan || cell.colspan || cell.scope) return true;
|
|
43648
|
+
}
|
|
43649
|
+
}
|
|
43650
|
+
}
|
|
43651
|
+
return false;
|
|
43652
|
+
}
|
|
43653
|
+
// --- content conversion (HTML -> TextAst) ---------------------------------
|
|
43654
|
+
/**
|
|
43655
|
+
* Convert a cell/li's child nodes into block-level TextAst nodes (paragraphs, lists, images).
|
|
43656
|
+
*/
|
|
43657
|
+
blockNodes(nodes) {
|
|
43658
|
+
const blocks = [];
|
|
43659
|
+
let inline = [];
|
|
43660
|
+
const flush = () => {
|
|
43661
|
+
const trimmed = this.trimInline(inline);
|
|
43662
|
+
inline = [];
|
|
43663
|
+
if (trimmed.length === 0) return;
|
|
43664
|
+
if (trimmed.length === 1 && trimmed[0].type === "imageInline") {
|
|
43665
|
+
blocks.push({ ...trimmed[0], type: "image" });
|
|
43666
|
+
} else {
|
|
43667
|
+
blocks.push({ type: "paragraph", content: trimmed, attrs: {} });
|
|
43668
|
+
}
|
|
43669
|
+
};
|
|
43670
|
+
for (const node of nodes) {
|
|
43671
|
+
if (!isElement(node)) {
|
|
43672
|
+
inline.push(...this.inlineText(node.value, []));
|
|
43673
|
+
continue;
|
|
43674
|
+
}
|
|
43675
|
+
const name = node.name;
|
|
43676
|
+
if (name === "p") {
|
|
43677
|
+
flush();
|
|
43678
|
+
const content = this.trimInline(this.inlineNodes(node.children, []));
|
|
43679
|
+
if (content.length > 0) {
|
|
43680
|
+
blocks.push({ type: "paragraph", content, attrs: {} });
|
|
43681
|
+
}
|
|
43682
|
+
} else if (this.isListTag(name)) {
|
|
43683
|
+
flush();
|
|
43684
|
+
const list = this.listNode(node);
|
|
43685
|
+
if (list) blocks.push(list);
|
|
43686
|
+
} else {
|
|
43687
|
+
inline.push(...this.inlineFromElement(node, []));
|
|
43688
|
+
}
|
|
43689
|
+
}
|
|
43690
|
+
flush();
|
|
43691
|
+
return blocks;
|
|
43692
|
+
}
|
|
43693
|
+
/** Convert child nodes to a flat list of inline TextNodes, applying the given marks. */
|
|
43694
|
+
inlineNodes(nodes, marks) {
|
|
43695
|
+
const out = [];
|
|
43696
|
+
for (const node of nodes) {
|
|
43697
|
+
if (!isElement(node)) {
|
|
43698
|
+
out.push(...this.inlineText(node.value, marks));
|
|
43699
|
+
} else {
|
|
43700
|
+
out.push(...this.inlineFromElement(node, marks));
|
|
43701
|
+
}
|
|
43702
|
+
}
|
|
43703
|
+
return out;
|
|
43704
|
+
}
|
|
43705
|
+
inlineFromElement(el, marks) {
|
|
43706
|
+
const name = el.name;
|
|
43707
|
+
if (name === "br") {
|
|
43708
|
+
return [{ type: "hardBreak" }];
|
|
43709
|
+
}
|
|
43710
|
+
if (name === "img") {
|
|
43711
|
+
return [this.imageNode(el, true)];
|
|
43712
|
+
}
|
|
43713
|
+
const markType = MARK_TAGS[name];
|
|
43714
|
+
if (markType) {
|
|
43715
|
+
return this.inlineNodes(el.children, [...marks, { type: markType }]);
|
|
43716
|
+
}
|
|
43717
|
+
if (name === "a") {
|
|
43718
|
+
const linkMark = {
|
|
43719
|
+
type: "link",
|
|
43720
|
+
attrs: {
|
|
43721
|
+
href: el.attrs.href ?? "",
|
|
43722
|
+
...el.attrs.target ? { target: el.attrs.target } : {}
|
|
43723
|
+
}
|
|
43724
|
+
};
|
|
43725
|
+
return this.inlineNodes(el.children, [...marks, linkMark]);
|
|
43726
|
+
}
|
|
43727
|
+
if (this.keepUnknownTags) {
|
|
43728
|
+
const open = this.serializeOpenTag(el);
|
|
43729
|
+
const close = `</${name}>`;
|
|
43730
|
+
return [
|
|
43731
|
+
...this.inlineText(open, marks),
|
|
43732
|
+
...this.inlineNodes(el.children, marks),
|
|
43733
|
+
...this.inlineText(close, marks)
|
|
43734
|
+
];
|
|
43735
|
+
}
|
|
43736
|
+
return this.inlineNodes(el.children, marks);
|
|
43737
|
+
}
|
|
43738
|
+
inlineText(raw, marks) {
|
|
43739
|
+
const text = decodeEntities(raw).replace(/\s+/g, " ");
|
|
43740
|
+
if (text === "") return [];
|
|
43741
|
+
const node = { text, type: "text" };
|
|
43742
|
+
if (marks.length > 0) node.marks = marks.map((m) => ({ ...m }));
|
|
43743
|
+
return [node];
|
|
43744
|
+
}
|
|
43745
|
+
serializeOpenTag(el) {
|
|
43746
|
+
const attrs = Object.entries(el.attrs).map(([k, v]) => v === "" ? ` ${k}` : ` ${k}="${v}"`).join("");
|
|
43747
|
+
return `<${el.name}${attrs}>`;
|
|
43748
|
+
}
|
|
43749
|
+
// --- lists ----------------------------------------------------------------
|
|
43750
|
+
isListTag(name) {
|
|
43751
|
+
return name === "ul" || name === "ol";
|
|
43752
|
+
}
|
|
43753
|
+
listNode(el) {
|
|
43754
|
+
const items = childElements(el, LI_TAGS);
|
|
43755
|
+
if (items.length === 0) return void 0;
|
|
43756
|
+
const isTask = el.name === "ul" && items.some((li) => {
|
|
43757
|
+
const firstEl = li.children.find(isElement);
|
|
43758
|
+
return firstEl?.name === "input" && firstEl.attrs.type?.toLowerCase() === "checkbox";
|
|
43759
|
+
});
|
|
43760
|
+
if (isTask) {
|
|
43761
|
+
return {
|
|
43762
|
+
type: "taskList",
|
|
43763
|
+
content: items.map((li) => this.taskItemNode(li))
|
|
43764
|
+
};
|
|
43765
|
+
}
|
|
43766
|
+
const type = this.listType(el);
|
|
43767
|
+
const node = {
|
|
43768
|
+
type,
|
|
43769
|
+
content: items.map((li) => this.listItemNode(li))
|
|
43770
|
+
};
|
|
43771
|
+
if (el.name === "ol") {
|
|
43772
|
+
const start = intAttr(el.attrs.start);
|
|
43773
|
+
if (start != null) {
|
|
43774
|
+
node.attrs = { start };
|
|
43775
|
+
}
|
|
43776
|
+
}
|
|
43777
|
+
return node;
|
|
43778
|
+
}
|
|
43779
|
+
listType(el) {
|
|
43780
|
+
if (el.name === "ol") {
|
|
43781
|
+
switch (el.attrs.type) {
|
|
43782
|
+
case "i":
|
|
43783
|
+
return "orderedListRomanLower";
|
|
43784
|
+
case "I":
|
|
43785
|
+
return "orderedListRoman";
|
|
43786
|
+
case "a":
|
|
43787
|
+
return "letteredListLower";
|
|
43788
|
+
case "A":
|
|
43789
|
+
return "letteredList";
|
|
43790
|
+
default:
|
|
43791
|
+
return "orderedList";
|
|
43792
|
+
}
|
|
43793
|
+
}
|
|
43794
|
+
const style = el.attrs.style ?? "";
|
|
43795
|
+
if (/list-style(-type)?\s*:\s*none/i.test(style)) return "noBulletList";
|
|
43796
|
+
return "bulletList";
|
|
43797
|
+
}
|
|
43798
|
+
listItemNode(li) {
|
|
43799
|
+
let content = this.blockNodes(li.children);
|
|
43800
|
+
if (content.length === 0) {
|
|
43801
|
+
content = [{ type: "paragraph", content: [], attrs: {} }];
|
|
43802
|
+
}
|
|
43803
|
+
return { type: "listItem", content };
|
|
43804
|
+
}
|
|
43805
|
+
taskItemNode(li) {
|
|
43806
|
+
const checkbox = li.children.find(
|
|
43807
|
+
(c) => isElement(c) && c.name === "input" && c.attrs.type?.toLowerCase() === "checkbox"
|
|
43808
|
+
);
|
|
43809
|
+
const checked = checkbox ? "checked" in checkbox.attrs : false;
|
|
43810
|
+
const rest = li.children.filter((c) => c !== checkbox);
|
|
43811
|
+
let content = this.blockNodes(rest);
|
|
43812
|
+
if (content.length === 0) {
|
|
43813
|
+
content = [{ type: "paragraph", content: [], attrs: {} }];
|
|
43814
|
+
}
|
|
43815
|
+
return { type: "taskItem", attrs: { checked }, content };
|
|
43816
|
+
}
|
|
43817
|
+
// --- images ---------------------------------------------------------------
|
|
43818
|
+
imageNode(el, inline) {
|
|
43819
|
+
const attrs = { src: el.attrs.src ?? "" };
|
|
43820
|
+
attrs.alt = el.attrs.alt ?? null;
|
|
43821
|
+
attrs.title = el.attrs.title ?? null;
|
|
43822
|
+
const width = intAttr(el.attrs.width);
|
|
43823
|
+
if (width != null) attrs.width = width;
|
|
43824
|
+
const height = intAttr(el.attrs.height);
|
|
43825
|
+
if (height != null) attrs.height = height;
|
|
43826
|
+
if (el.attrs.class) attrs.class = el.attrs.class;
|
|
43827
|
+
return { type: inline ? "imageInline" : "image", attrs };
|
|
43828
|
+
}
|
|
43829
|
+
// --- whitespace -----------------------------------------------------------
|
|
43830
|
+
/** Trim leading/trailing whitespace from inline content (edges only). */
|
|
43831
|
+
trimInline(nodes) {
|
|
43832
|
+
const out = nodes.slice();
|
|
43833
|
+
const isWs = (n) => n.type === "text" && /^\s*$/.test(n.text ?? "");
|
|
43834
|
+
while (out.length > 0 && isWs(out[0])) out.shift();
|
|
43835
|
+
if (out.length > 0 && out[0].type === "text") {
|
|
43836
|
+
out[0] = { ...out[0], text: (out[0].text ?? "").replace(/^\s+/, "") };
|
|
43837
|
+
}
|
|
43838
|
+
while (out.length > 0 && isWs(out[out.length - 1])) out.pop();
|
|
43839
|
+
if (out.length > 0 && out[out.length - 1].type === "text") {
|
|
43840
|
+
const last = out.length - 1;
|
|
43841
|
+
out[last] = { ...out[last], text: (out[last].text ?? "").replace(/\s+$/, "") };
|
|
43842
|
+
}
|
|
43843
|
+
return out.filter((n) => !(n.type === "text" && n.text === ""));
|
|
43844
|
+
}
|
|
43845
|
+
};
|
|
43846
|
+
|
|
41187
43847
|
// src/BitmarkParserGenerator.ts
|
|
41188
43848
|
var Input = {
|
|
41189
43849
|
/**
|
|
@@ -41221,7 +43881,11 @@ var InputFormat = {
|
|
|
41221
43881
|
/**
|
|
41222
43882
|
* Input is plain text
|
|
41223
43883
|
*/
|
|
41224
|
-
plainText: "plainText"
|
|
43884
|
+
plainText: "plainText",
|
|
43885
|
+
/**
|
|
43886
|
+
* Input is HTML (used by convertHtmlTable)
|
|
43887
|
+
*/
|
|
43888
|
+
html: "html"
|
|
41225
43889
|
};
|
|
41226
43890
|
var BitmarkParserGenerator = class {
|
|
41227
43891
|
ast = new Ast();
|
|
@@ -41342,8 +44006,8 @@ var BitmarkParserGenerator = class {
|
|
|
41342
44006
|
}
|
|
41343
44007
|
if (!opts.inputFormat || opts.inputFormat === Input.file) {
|
|
41344
44008
|
if (env.isNode) {
|
|
41345
|
-
if (inputIsString &&
|
|
41346
|
-
inStr =
|
|
44009
|
+
if (inputIsString && fs4.existsSync(inStr)) {
|
|
44010
|
+
inStr = fs4.readFileSync(inStr, {
|
|
41347
44011
|
encoding: "utf8"
|
|
41348
44012
|
});
|
|
41349
44013
|
}
|
|
@@ -41389,7 +44053,7 @@ var BitmarkParserGenerator = class {
|
|
|
41389
44053
|
const textGenerator = new PlainTextGenerator();
|
|
41390
44054
|
const str = textGenerator.generate(json);
|
|
41391
44055
|
if (opts.outputFile) {
|
|
41392
|
-
|
|
44056
|
+
fs4.writeFileSync(opts.outputFile, str, {
|
|
41393
44057
|
encoding: "utf8"
|
|
41394
44058
|
});
|
|
41395
44059
|
} else {
|
|
@@ -41424,7 +44088,7 @@ var BitmarkParserGenerator = class {
|
|
|
41424
44088
|
const textGenerator = new PlainTextGenerator();
|
|
41425
44089
|
const str = textGenerator.generate(json);
|
|
41426
44090
|
if (opts.outputFile) {
|
|
41427
|
-
|
|
44091
|
+
fs4.writeFileSync(opts.outputFile, str, {
|
|
41428
44092
|
encoding: "utf8"
|
|
41429
44093
|
});
|
|
41430
44094
|
} else {
|
|
@@ -41449,7 +44113,7 @@ var BitmarkParserGenerator = class {
|
|
|
41449
44113
|
const textGenerator = new PlainTextGenerator();
|
|
41450
44114
|
const str = textGenerator.generate(json);
|
|
41451
44115
|
if (opts.outputFile) {
|
|
41452
|
-
|
|
44116
|
+
fs4.writeFileSync(opts.outputFile, str, {
|
|
41453
44117
|
encoding: "utf8"
|
|
41454
44118
|
});
|
|
41455
44119
|
} else {
|
|
@@ -41533,8 +44197,8 @@ var BitmarkParserGenerator = class {
|
|
|
41533
44197
|
}
|
|
41534
44198
|
if (!opts.inputFormat || opts.inputFormat === Input.file) {
|
|
41535
44199
|
if (env.isNode) {
|
|
41536
|
-
if (inputIsString &&
|
|
41537
|
-
inStr =
|
|
44200
|
+
if (inputIsString && fs4.existsSync(inStr)) {
|
|
44201
|
+
inStr = fs4.readFileSync(inStr, {
|
|
41538
44202
|
encoding: "utf8"
|
|
41539
44203
|
});
|
|
41540
44204
|
}
|
|
@@ -41592,8 +44256,8 @@ var BitmarkParserGenerator = class {
|
|
|
41592
44256
|
const opts = Object.assign({}, options);
|
|
41593
44257
|
if (!opts.inputFormat || opts.inputFormat === Input.file) {
|
|
41594
44258
|
if (env.isNode) {
|
|
41595
|
-
if (inputIsString &&
|
|
41596
|
-
inStr =
|
|
44259
|
+
if (inputIsString && fs4.existsSync(inStr)) {
|
|
44260
|
+
inStr = fs4.readFileSync(inStr, {
|
|
41597
44261
|
encoding: "utf8"
|
|
41598
44262
|
});
|
|
41599
44263
|
}
|
|
@@ -41653,8 +44317,8 @@ var BitmarkParserGenerator = class {
|
|
|
41653
44317
|
}
|
|
41654
44318
|
if (!opts.inputFormat || opts.inputFormat === Input.file) {
|
|
41655
44319
|
if (env.isNode) {
|
|
41656
|
-
if (inputIsString &&
|
|
41657
|
-
inStr =
|
|
44320
|
+
if (inputIsString && fs4.existsSync(inStr)) {
|
|
44321
|
+
inStr = fs4.readFileSync(inStr, {
|
|
41658
44322
|
encoding: "utf8"
|
|
41659
44323
|
});
|
|
41660
44324
|
}
|
|
@@ -41677,8 +44341,8 @@ var BitmarkParserGenerator = class {
|
|
|
41677
44341
|
strRes = this.jsonStringifyPrettify(preRes, jsonOptions, true);
|
|
41678
44342
|
}
|
|
41679
44343
|
const flag = fileOptions.append ? "a" : "w";
|
|
41680
|
-
|
|
41681
|
-
|
|
44344
|
+
fs4.ensureDirSync(path3.dirname(output));
|
|
44345
|
+
fs4.writeFileSync(output, strRes, {
|
|
41682
44346
|
flag
|
|
41683
44347
|
});
|
|
41684
44348
|
} else {
|
|
@@ -41738,6 +44402,87 @@ var BitmarkParserGenerator = class {
|
|
|
41738
44402
|
const res = generator.generate(data);
|
|
41739
44403
|
return res;
|
|
41740
44404
|
}
|
|
44405
|
+
/**
|
|
44406
|
+
* Convert between HTML tables and bitmark tables.
|
|
44407
|
+
*
|
|
44408
|
+
* Direction is auto-detected (override with `options.inputFormat`):
|
|
44409
|
+
* - HTML input: every `<table>` is extracted and emitted as a `[.table-extended]` (or `[.table]`)
|
|
44410
|
+
* bit; output as bitmark (default), JSON, AST, or HTML (re-emit).
|
|
44411
|
+
* - bitmark / JSON / AST input: each `table` / `table-extended` bit is rendered as an HTML
|
|
44412
|
+
* `<table>` fragment.
|
|
44413
|
+
*
|
|
44414
|
+
* Cell content is reproduced verbatim except inline conversion to bitmark marks (bold, italic,
|
|
44415
|
+
* superscript, etc.), with lists and images mapped to their bitmark equivalents.
|
|
44416
|
+
*
|
|
44417
|
+
* @param input - HTML, bitmark, JSON or AST string; or a parsed JSON/AST value; or a file path.
|
|
44418
|
+
* @param options - the conversion options
|
|
44419
|
+
* @returns the converted output as a string (or object for JSON/AST), or void when writing a file
|
|
44420
|
+
* @throws Error if any error occurs
|
|
44421
|
+
*/
|
|
44422
|
+
convertHtmlTable(input, options) {
|
|
44423
|
+
const opts = Object.assign({}, options);
|
|
44424
|
+
if (env.isBrowser && opts.outputFile) {
|
|
44425
|
+
throw new Error("Cannot write to file in browser environment");
|
|
44426
|
+
}
|
|
44427
|
+
let inStr = input;
|
|
44428
|
+
const inputIsString = typeof input === "string";
|
|
44429
|
+
if (env.isNode && inputIsString && fs4.existsSync(inStr)) {
|
|
44430
|
+
inStr = fs4.readFileSync(inStr, { encoding: "utf8" });
|
|
44431
|
+
}
|
|
44432
|
+
const isHtmlString = (s) => {
|
|
44433
|
+
const t = s.replace(/^\s+/, "");
|
|
44434
|
+
return t.startsWith("<") && /<table/i.test(t);
|
|
44435
|
+
};
|
|
44436
|
+
let htmlInput;
|
|
44437
|
+
if (opts.inputFormat === InputFormat.html) {
|
|
44438
|
+
htmlInput = true;
|
|
44439
|
+
} else if (opts.inputFormat != null) {
|
|
44440
|
+
htmlInput = false;
|
|
44441
|
+
} else {
|
|
44442
|
+
htmlInput = inputIsString && isHtmlString(inStr);
|
|
44443
|
+
}
|
|
44444
|
+
if (htmlInput) {
|
|
44445
|
+
const parser2 = new HtmlTableParser();
|
|
44446
|
+
const bits = parser2.parse(inStr, {
|
|
44447
|
+
tableFormat: opts.tableFormat ?? "table-extended",
|
|
44448
|
+
keepUnknownTags: opts.keepUnknownTags,
|
|
44449
|
+
onWarning: (m) => console.warn(m)
|
|
44450
|
+
});
|
|
44451
|
+
const outputFormat2 = opts.outputFormat ?? Output.bitmark;
|
|
44452
|
+
if (outputFormat2 === "html") {
|
|
44453
|
+
const json2 = this.convert(bits, { outputFormat: Output.json });
|
|
44454
|
+
return this.outputHtml(new HtmlTableGenerator().generate(json2), opts);
|
|
44455
|
+
}
|
|
44456
|
+
return this.convert(bits, {
|
|
44457
|
+
outputFormat: Enum(Output).fromValue(outputFormat2),
|
|
44458
|
+
outputFile: opts.outputFile,
|
|
44459
|
+
fileOptions: opts.fileOptions,
|
|
44460
|
+
jsonOptions: opts.jsonOptions,
|
|
44461
|
+
bitmarkOptions: opts.bitmarkOptions
|
|
44462
|
+
});
|
|
44463
|
+
}
|
|
44464
|
+
const outputFormat = opts.outputFormat ?? "html";
|
|
44465
|
+
if (outputFormat !== "html") {
|
|
44466
|
+
throw new Error(
|
|
44467
|
+
"convertHtmlTable: non-HTML input can only be converted to HTML. Use convert() for bitmark/JSON/AST conversions."
|
|
44468
|
+
);
|
|
44469
|
+
}
|
|
44470
|
+
const json = this.convert(inputIsString ? inStr : input, { outputFormat: Output.json });
|
|
44471
|
+
return this.outputHtml(new HtmlTableGenerator().generate(json), opts);
|
|
44472
|
+
}
|
|
44473
|
+
/**
|
|
44474
|
+
* Write an HTML string to the configured output file, or return it.
|
|
44475
|
+
*/
|
|
44476
|
+
outputHtml(html, opts) {
|
|
44477
|
+
if (opts.outputFile) {
|
|
44478
|
+
const output = opts.outputFile.toString();
|
|
44479
|
+
const flag = opts.fileOptions?.append ? "a" : "w";
|
|
44480
|
+
fs4.ensureDirSync(path3.dirname(output));
|
|
44481
|
+
fs4.writeFileSync(output, html, { flag });
|
|
44482
|
+
return;
|
|
44483
|
+
}
|
|
44484
|
+
return html;
|
|
44485
|
+
}
|
|
41741
44486
|
/**
|
|
41742
44487
|
* Breakscape bitmark text.
|
|
41743
44488
|
*
|
|
@@ -41769,8 +44514,8 @@ var BitmarkParserGenerator = class {
|
|
|
41769
44514
|
}
|
|
41770
44515
|
if (!opts.inputFormat || opts.inputFormat === Input.file) {
|
|
41771
44516
|
if (env.isNode) {
|
|
41772
|
-
if (inputIsString &&
|
|
41773
|
-
inStr =
|
|
44517
|
+
if (inputIsString && fs4.existsSync(inStr)) {
|
|
44518
|
+
inStr = fs4.readFileSync(inStr, {
|
|
41774
44519
|
encoding: "utf8"
|
|
41775
44520
|
});
|
|
41776
44521
|
}
|
|
@@ -41783,8 +44528,8 @@ var BitmarkParserGenerator = class {
|
|
|
41783
44528
|
if (opts.outputFile) {
|
|
41784
44529
|
const output = opts.outputFile.toString();
|
|
41785
44530
|
const flag = fileOptions.append ? "a" : "w";
|
|
41786
|
-
|
|
41787
|
-
|
|
44531
|
+
fs4.ensureDirSync(path3.dirname(output));
|
|
44532
|
+
fs4.writeFileSync(output, res, {
|
|
41788
44533
|
flag
|
|
41789
44534
|
});
|
|
41790
44535
|
} else {
|
|
@@ -41822,8 +44567,8 @@ var BitmarkParserGenerator = class {
|
|
|
41822
44567
|
}
|
|
41823
44568
|
if (!opts.inputFormat || opts.inputFormat === Input.file) {
|
|
41824
44569
|
if (env.isNode) {
|
|
41825
|
-
if (inputIsString &&
|
|
41826
|
-
inStr =
|
|
44570
|
+
if (inputIsString && fs4.existsSync(inStr)) {
|
|
44571
|
+
inStr = fs4.readFileSync(inStr, {
|
|
41827
44572
|
encoding: "utf8"
|
|
41828
44573
|
});
|
|
41829
44574
|
}
|
|
@@ -41836,8 +44581,8 @@ var BitmarkParserGenerator = class {
|
|
|
41836
44581
|
if (opts.outputFile) {
|
|
41837
44582
|
const output = opts.outputFile.toString();
|
|
41838
44583
|
const flag = fileOptions.append ? "a" : "w";
|
|
41839
|
-
|
|
41840
|
-
|
|
44584
|
+
fs4.ensureDirSync(path3.dirname(output));
|
|
44585
|
+
fs4.writeFileSync(output, res, {
|
|
41841
44586
|
flag
|
|
41842
44587
|
});
|
|
41843
44588
|
} else {
|
|
@@ -41892,8 +44637,8 @@ var BitmarkParserGenerator = class {
|
|
|
41892
44637
|
init();
|
|
41893
44638
|
async function readInput(input) {
|
|
41894
44639
|
if (input !== void 0) {
|
|
41895
|
-
if (await
|
|
41896
|
-
return
|
|
44640
|
+
if (await fs4.pathExists(input)) {
|
|
44641
|
+
return fs4.readFile(input, "utf8");
|
|
41897
44642
|
}
|
|
41898
44643
|
return input;
|
|
41899
44644
|
}
|
|
@@ -41906,8 +44651,8 @@ async function readInput(input) {
|
|
|
41906
44651
|
async function writeOutput(content, outputFile, append = false) {
|
|
41907
44652
|
const text = typeof content === "string" ? content : JSON.stringify(content);
|
|
41908
44653
|
if (outputFile) {
|
|
41909
|
-
await
|
|
41910
|
-
await
|
|
44654
|
+
await fs4.ensureDir(path3.dirname(outputFile));
|
|
44655
|
+
await fs4.writeFile(outputFile, text, { flag: append ? "a" : "w" });
|
|
41911
44656
|
} else {
|
|
41912
44657
|
console.log(text);
|
|
41913
44658
|
}
|
|
@@ -42056,6 +44801,70 @@ Examples:
|
|
|
42056
44801
|
);
|
|
42057
44802
|
return cmd;
|
|
42058
44803
|
}
|
|
44804
|
+
var OUTPUT_FORMAT_CHOICES2 = ["html", "bitmark", "json", "ast"];
|
|
44805
|
+
var INPUT_FORMAT_CHOICES = ["html", "bitmark", "json"];
|
|
44806
|
+
var TABLE_FORMAT_CHOICES = ["table", "table-extended"];
|
|
44807
|
+
function createConvertHtmlTableCommand() {
|
|
44808
|
+
const bpg = new BitmarkParserGenerator();
|
|
44809
|
+
const cmd = new Command("convertHtmlTable").description("Convert between HTML tables and bitmark tables").argument(
|
|
44810
|
+
"[input]",
|
|
44811
|
+
"file to read, or HTML / bitmark / json string. If not specified, input will be from <stdin>"
|
|
44812
|
+
).addOption(
|
|
44813
|
+
new Option(
|
|
44814
|
+
"-f, --format <format>",
|
|
44815
|
+
"output format (default: bitmark for HTML input, html for bitmark/json input)"
|
|
44816
|
+
).choices(OUTPUT_FORMAT_CHOICES2)
|
|
44817
|
+
).addOption(
|
|
44818
|
+
new Option("--inputFormat <format>", "force input format (auto-detected by default)").choices(
|
|
44819
|
+
INPUT_FORMAT_CHOICES
|
|
44820
|
+
)
|
|
44821
|
+
).addOption(
|
|
44822
|
+
new Option("--tableFormat <format>", "bit type to emit for HTML input").choices(TABLE_FORMAT_CHOICES).default("table-extended")
|
|
44823
|
+
).option("--keepUnknownTags", "keep unmappable HTML tags as literal text (HTML input only)").option("-p, --pretty", "prettify the JSON output with indent").option(
|
|
44824
|
+
"--indent <indent>",
|
|
44825
|
+
"prettify indent (default: 2 when --pretty is set)",
|
|
44826
|
+
(v) => Number.parseInt(v, 10)
|
|
44827
|
+
).option("-a, --append", "append to the output file (default is to overwrite)").option("-o, --output <file>", "output file. If not specified, output will be to <stdout>").action(async (input, options) => {
|
|
44828
|
+
try {
|
|
44829
|
+
const dataIn = await readInput(input);
|
|
44830
|
+
const prettify = options.pretty ? options.indent ?? 2 : void 0;
|
|
44831
|
+
const inputFormat = options.inputFormat === "html" ? InputFormat.html : options.inputFormat != null ? InputFormat.bitmark : void 0;
|
|
44832
|
+
const result = bpg.convertHtmlTable(dataIn, {
|
|
44833
|
+
inputFormat,
|
|
44834
|
+
outputFormat: options.format,
|
|
44835
|
+
tableFormat: Enum({ table: "table", "table-extended": "table-extended" }).fromValue(
|
|
44836
|
+
options.tableFormat
|
|
44837
|
+
),
|
|
44838
|
+
keepUnknownTags: options.keepUnknownTags,
|
|
44839
|
+
jsonOptions: {
|
|
44840
|
+
prettify
|
|
44841
|
+
}
|
|
44842
|
+
});
|
|
44843
|
+
let outputValue = result ?? "";
|
|
44844
|
+
if (typeof outputValue !== "string") {
|
|
44845
|
+
outputValue = formatJson(outputValue, options.pretty, options.indent);
|
|
44846
|
+
}
|
|
44847
|
+
await writeOutput(outputValue, options.output, options.append);
|
|
44848
|
+
} catch (error) {
|
|
44849
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
44850
|
+
process.exit(1);
|
|
44851
|
+
}
|
|
44852
|
+
}).addHelpText(
|
|
44853
|
+
"after",
|
|
44854
|
+
`
|
|
44855
|
+
Examples:
|
|
44856
|
+
$ bitmark-parser convertHtmlTable '<table><tr><th>Name</th></tr><tr><td>John</td></tr></table>'
|
|
44857
|
+
|
|
44858
|
+
$ bitmark-parser convertHtmlTable input.html -o output.bitmark
|
|
44859
|
+
|
|
44860
|
+
$ bitmark-parser convertHtmlTable input.html -f json -p -o output.json
|
|
44861
|
+
|
|
44862
|
+
$ bitmark-parser convertHtmlTable input.bitmark -o output.html
|
|
44863
|
+
|
|
44864
|
+
$ bitmark-parser convertHtmlTable --tableFormat table input.html`
|
|
44865
|
+
);
|
|
44866
|
+
return cmd;
|
|
44867
|
+
}
|
|
42059
44868
|
var TEXT_FORMAT_CHOICES = (() => {
|
|
42060
44869
|
const choices = enumChoices(BodyTextFormat);
|
|
42061
44870
|
const preferred = BodyTextFormat.bitmarkPlusPlus;
|
|
@@ -42112,7 +44921,7 @@ Examples:
|
|
|
42112
44921
|
);
|
|
42113
44922
|
return cmd;
|
|
42114
44923
|
}
|
|
42115
|
-
var
|
|
44924
|
+
var INPUT_FORMAT_CHOICES2 = enumChoices(InputFormat);
|
|
42116
44925
|
function createExtractPlainTextCommand() {
|
|
42117
44926
|
const bpg = new BitmarkParserGenerator();
|
|
42118
44927
|
const cmd = new Command("extractPlainText").description("Extract plain text from bitmark, bitmark text, or JSON").argument(
|
|
@@ -42122,7 +44931,7 @@ function createExtractPlainTextCommand() {
|
|
|
42122
44931
|
new Option(
|
|
42123
44932
|
"-f, --inputFormat <format>",
|
|
42124
44933
|
"force input format (auto-detected by default)"
|
|
42125
|
-
).choices([...
|
|
44934
|
+
).choices([...INPUT_FORMAT_CHOICES2])
|
|
42126
44935
|
).option("-a, --append", "append to the output file (default is to overwrite)").option("-o, --output <file>", "output file. If not specified, output will be to <stdout>").action(async (input, options) => {
|
|
42127
44936
|
try {
|
|
42128
44937
|
const dataIn = await readInput(input);
|
|
@@ -42274,6 +45083,7 @@ async function asyncInit() {
|
|
|
42274
45083
|
program.outputHelp();
|
|
42275
45084
|
});
|
|
42276
45085
|
program.addCommand(createConvertCommand());
|
|
45086
|
+
program.addCommand(createConvertHtmlTableCommand());
|
|
42277
45087
|
program.addCommand(createConvertTextCommand());
|
|
42278
45088
|
program.addCommand(createExtractPlainTextCommand());
|
|
42279
45089
|
program.addCommand(createBreakscapeCommand());
|