@memizy/oqse 0.1.5 → 1.0.0-rc.5
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/LICENSE +21 -21
- package/README.md +90 -75
- package/dist/answers.cjs +1 -0
- package/dist/answers.js +78 -0
- package/dist/assets.cjs +1 -0
- package/dist/assets.js +7 -0
- package/dist/checkAnswer.cjs +1 -0
- package/dist/checkAnswer.js +166 -0
- package/dist/compatibility.cjs +1 -0
- package/dist/compatibility.js +32 -0
- package/dist/features.cjs +1 -0
- package/dist/features.js +58 -0
- package/dist/fileValidation.cjs +2 -0
- package/dist/fileValidation.js +230 -0
- package/dist/headerValidation.cjs +1 -0
- package/dist/headerValidation.js +26 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +19 -1029
- package/dist/manifest.cjs +1 -0
- package/dist/manifest.js +75 -0
- package/dist/manifestValidation.cjs +1 -0
- package/dist/manifestValidation.js +69 -0
- package/dist/markdownSet.cjs +11 -0
- package/dist/markdownSet.js +232 -0
- package/dist/markdownUtils.cjs +5 -0
- package/dist/markdownUtils.js +47 -0
- package/dist/oqse.cjs +1 -0
- package/dist/oqse.js +108 -0
- package/dist/oqseValidation.cjs +1 -0
- package/dist/oqseValidation.js +586 -0
- package/dist/plainText.cjs +7 -0
- package/dist/plainText.js +9 -0
- package/dist/progressValidation.cjs +1 -0
- package/dist/progressValidation.js +52 -0
- package/dist/richTextFields.cjs +1 -0
- package/dist/richTextFields.js +101 -0
- package/dist/richTextProcessor.cjs +1 -0
- package/dist/richTextProcessor.js +51 -0
- package/dist/src/answers.d.ts +54 -0
- package/dist/src/answers.test.d.ts +1 -0
- package/dist/src/assets.d.ts +6 -0
- package/dist/src/checkAnswer.d.ts +2 -0
- package/dist/src/checkAnswer.test.d.ts +1 -0
- package/dist/src/compatibility.d.ts +40 -0
- package/dist/src/compatibility.test.d.ts +1 -0
- package/dist/src/features.d.ts +12 -0
- package/dist/src/features.test.d.ts +1 -0
- package/dist/src/fileValidation.d.ts +63 -0
- package/dist/src/fileValidation.test.d.ts +1 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/manifest.d.ts +23 -14
- package/dist/src/manifestValidation.d.ts +13 -13
- package/dist/src/markdownSet.d.ts +47 -0
- package/dist/src/markdownSet.test.d.ts +1 -0
- package/dist/src/markdownUtils.d.ts +21 -0
- package/dist/src/oqse.d.ts +167 -146
- package/dist/src/oqseValidation.d.ts +3027 -710
- package/dist/src/plainText.d.ts +14 -0
- package/dist/src/plainText.test.d.ts +1 -0
- package/dist/src/progress.d.ts +2 -2
- package/dist/src/richTextFields.d.ts +8 -0
- package/dist/src/richTextProcessor.d.ts +33 -11
- package/dist/src/specExamples.test.d.ts +5 -0
- package/dist/src/utils.d.ts +4 -0
- package/dist/utils.cjs +1 -0
- package/dist/utils.js +29 -0
- package/oqse-header.md +56 -0
- package/oqse-manifest.md +249 -0
- package/oqse-progress.md +172 -0
- package/oqse.md +2232 -0
- package/package.json +20 -5
- package/schemas/{oqse-header-v0.1.json → oqse-header-v1.0.json} +11 -11
- package/schemas/{oqse-manifest-v0.1.json → oqse-manifest-v1.0.json} +37 -18
- package/schemas/{oqse-progress-v0.1.json → oqse-progress-v1.0.json} +1 -1
- package/schemas/oqse-v1.0.json +8268 -0
- package/schemas/oqse-v0.1.json +0 -7626
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 memizy
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 memizy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,75 +1,90 @@
|
|
|
1
|
-
# OQSE SDK
|
|
2
|
-
|
|
3
|
-
Official TypeScript SDK for the Open Quiz & Study Exchange ecosystem, published as @memizy/oqse.
|
|
4
|
-
|
|
5
|
-
🚀 **[OQSE Web Validator](https://memizy.github.io/
|
|
6
|
-
|
|
7
|
-
This repository contains:
|
|
8
|
-
- The human-readable specifications split into four standalone documents:
|
|
9
|
-
- [OQSE (Study Sets)](./oqse.md)
|
|
10
|
-
- [OQSEM (Application Manifests)](./oqse-manifest.md)
|
|
11
|
-
- [OQSEP (User Progress)](./oqse-progress.md)
|
|
12
|
-
- [OQSEH (Set Headers)](./oqse-header.md)
|
|
13
|
-
- Runtime validators built with Zod.
|
|
14
|
-
- Manually authored TypeScript types for developer ergonomics.
|
|
15
|
-
- Auto-generated JSON Schemas distributed with the package.
|
|
16
|
-
|
|
17
|
-
## Package
|
|
18
|
-
|
|
19
|
-
- Name: @memizy/oqse
|
|
20
|
-
- Purpose: validate, parse, and type OQSE, OQSEM (Application Manifest), and OQSEP (User Progress) data.
|
|
21
|
-
|
|
22
|
-
##
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
1
|
+
# OQSE SDK
|
|
2
|
+
|
|
3
|
+
Official TypeScript SDK for the Open Quiz & Study Exchange ecosystem, published as @memizy/oqse.
|
|
4
|
+
|
|
5
|
+
🚀 **[OQSE Web Validator](https://memizy.github.io/memizy/)** - Online tool for easy testing and debugging of OQSE JSON structures in the browser (source: [`docs/`](./docs/), published by GitHub Pages).
|
|
6
|
+
|
|
7
|
+
This repository contains:
|
|
8
|
+
- The human-readable specifications split into four standalone documents:
|
|
9
|
+
- [OQSE (Study Sets)](./oqse.md)
|
|
10
|
+
- [OQSEM (Application Manifests)](./oqse-manifest.md)
|
|
11
|
+
- [OQSEP (User Progress)](./oqse-progress.md)
|
|
12
|
+
- [OQSEH (Set Headers)](./oqse-header.md)
|
|
13
|
+
- Runtime validators built with Zod.
|
|
14
|
+
- Manually authored TypeScript types for developer ergonomics.
|
|
15
|
+
- Auto-generated JSON Schemas distributed with the package.
|
|
16
|
+
|
|
17
|
+
## Package
|
|
18
|
+
|
|
19
|
+
- Name: @memizy/oqse
|
|
20
|
+
- Purpose: validate, parse, and type OQSE, OQSEM (Application Manifest), and OQSEP (User Progress) data.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import {
|
|
26
|
+
loadOQSEFile, // tolerant import: skips invalid items, returns errors + warnings
|
|
27
|
+
validateOQSEFile, // strict save/export validation (throws OQSEValidationError)
|
|
28
|
+
resolveAsset, // <asset:key /> lookup: item.assets first, then meta.assets
|
|
29
|
+
checkAnswer, // is a learner's answer correct? (Answer Formats in the specification)
|
|
30
|
+
parseMarkdownSet, // OQSE Markdown (.oqse.md) -> OQSE file
|
|
31
|
+
serializeMarkdownSet, // OQSE file of notes -> OQSE Markdown
|
|
32
|
+
} from '@memizy/oqse';
|
|
33
|
+
|
|
34
|
+
const { success, data, errors, warnings } = loadOQSEFile(JSON.parse(text));
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Schemas
|
|
38
|
+
|
|
39
|
+
The auto-generated JSON Schemas are published with this repository and are available at the following URLs:
|
|
40
|
+
|
|
41
|
+
- Core OQSE Schema: https://cdn.jsdelivr.net/npm/@memizy/oqse@1/schemas/oqse-v1.0.json
|
|
42
|
+
- Manifest Schema: https://cdn.jsdelivr.net/npm/@memizy/oqse@1/schemas/oqse-manifest-v1.0.json
|
|
43
|
+
- Progress Schema: https://cdn.jsdelivr.net/npm/@memizy/oqse@1/schemas/oqse-progress-v1.0.json
|
|
44
|
+
- Header Schema: https://cdn.jsdelivr.net/npm/@memizy/oqse@1/schemas/oqse-header-v1.0.json
|
|
45
|
+
|
|
46
|
+
## Architecture & Single Source of Truth
|
|
47
|
+
|
|
48
|
+
The human-readable Markdown specifications are the **absolute single source of truth** for the OQSE ecosystem.
|
|
49
|
+
|
|
50
|
+
1. **The Specifications**
|
|
51
|
+
The four Markdown files ([oqse.md](./oqse.md), [oqse-manifest.md](./oqse-manifest.md), [oqse-progress.md](./oqse-progress.md), and [oqse-header.md](./oqse-header.md)) constitute the normative documentation for concepts, rules, and semantics.
|
|
52
|
+
|
|
53
|
+
2. **Zod Schemas**
|
|
54
|
+
Runtime validators (`*Validation.ts`) are implementations. They **MUST match** the rules defined in the Markdown specs. They are not the source of truth; if they disagree with the text spec, the code is considered a bug.
|
|
55
|
+
|
|
56
|
+
3. **TypeScript Types**
|
|
57
|
+
TypeScript types (`oqse.ts`, `manifest.ts`, `progress.ts`) manually mirror the Zod schemas for better Developer Experience. They must also strictly adhere to the Markdown specifications.
|
|
58
|
+
|
|
59
|
+
4. JSON schemas are strictly auto-generated using bun run generate:schemas and contributors MUST NOT edit them manually.
|
|
60
|
+
If validation behavior changes, update the Zod schemas first, then regenerate.
|
|
61
|
+
|
|
62
|
+
## Development
|
|
63
|
+
|
|
64
|
+
Install dependencies (from the monorepo root):
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
bun install
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Build and regenerate schemas:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
bun run build
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Generate only JSON schemas:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
bun run generate:schemas
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Run tests:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
bun run test
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Contributing Rule
|
|
89
|
+
|
|
90
|
+
Never edit files in schemas/ by hand. Always regenerate via bun run generate:schemas.
|
package/dist/answers.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e,t={}){let n=e.normalize(`NFC`).replace(/\s+/gu,` `).trim();return t.caseSensitive||(n=n.toLowerCase()),(t.ignoreDiacritics??!0)&&(n=n.normalize(`NFD`).replace(/\p{M}/gu,``).normalize(`NFC`)),n}function t(e){let t=[...e].length;return t<=3?0:t<=7?1:2}function n(n,i,a={}){let o=e(n,a),s=e(i,a);if(o===s)return!0;if(!a.acceptPartial)return!1;let c=t(s);return c>0&&r(o,s,c)<=c}function r(e,t,n=1/0){let r=[...e],i=[...t];if(Math.abs(r.length-i.length)>n)return n+1;let a=[],o=Array.from({length:i.length+1},(e,t)=>t);for(let e=1;e<=r.length;e++){let t=[e],s=e;for(let n=1;n<=i.length;n++){let c=r[e-1]===i[n-1]?0:1,l=Math.min(o[n]+1,t[n-1]+1,o[n-1]+c);e>1&&n>1&&r[e-1]===i[n-2]&&r[e-2]===i[n-1]&&(l=Math.min(l,a[n-2]+1)),t[n]=l,s=Math.min(s,l)}if(s>n)return n+1;a=o,o=t}return o[i.length]}var i=/^(-?)(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(Z|[+-]\d{2}:\d{2})?)?$/,a={year:0,month:1,day:2,datetime:3};function o(e){let t=i.exec(e);if(!t)return null;let n=(t[1]?-1:1)*Number(t[2]);if(n===0)return null;let r=Number(t[3]),a=s(n<0?n+1:n,r,Number(t[4])),o=t[5]!==void 0,c=0;if(o){c=((Number(t[5])*60+Number(t[6]))*60+Number(t[7]))*1e3+Number((t[8]??`0`).padEnd(3,`0`));let e=t[9];if(e&&e!==`Z`){let t=e[0]===`-`?-1:1;c-=t*(Number(e.slice(1,3))*60+Number(e.slice(4,6)))*6e4}}return{year:n,month:r,days:a,instant:a*864e5+c,hasTime:o}}function s(e,t,n){let r=t<=2?e-1:e,i=Math.floor(r/400),a=r-i*400,o=Math.floor((153*(t+(t>2?-3:9))+2)/5)+n-1,s=a*365+Math.floor(a/4)-Math.floor(a/100)+o;return i*146097+s-719468}function c(e){return o(e)!==null}function l(e,t){let n=o(e.date),r=o(t.date);if(!n||!r)throw Error(`Invalid timeline date: ${n?t.date:e.date}`);let i=(e,t)=>a[t??(e.hasTime?`datetime`:`day`)],s=Math.min(i(n,e.precision),i(r,t.precision));return s===a.year?Math.sign(n.year-r.year):s===a.month?Math.sign(n.year-r.year||n.month-r.month):s===a.day?Math.sign(n.days-r.days):Math.sign(n.instant-r.instant)}function u(e){for(let t=0;t<e.length;t++)for(let n=t+1;n<e.length;n++)if(l(e[t],e[n])>0)return!1;return!0}exports.allowedTypos=t,exports.compareTimelineEvents=l,exports.editDistance=r,exports.isTimelineDate=c,exports.isTimelineOrderCorrect=u,exports.normalizeAnswerText=e,exports.textAnswerMatches=n;
|
package/dist/answers.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region src/answers.ts
|
|
2
|
+
function e(e, t = {}) {
|
|
3
|
+
let n = e.normalize("NFC").replace(/\s+/gu, " ").trim();
|
|
4
|
+
return t.caseSensitive || (n = n.toLowerCase()), (t.ignoreDiacritics ?? !0) && (n = n.normalize("NFD").replace(/\p{M}/gu, "").normalize("NFC")), n;
|
|
5
|
+
}
|
|
6
|
+
function t(e) {
|
|
7
|
+
let t = [...e].length;
|
|
8
|
+
return t <= 3 ? 0 : t <= 7 ? 1 : 2;
|
|
9
|
+
}
|
|
10
|
+
function n(n, i, a = {}) {
|
|
11
|
+
let o = e(n, a), s = e(i, a);
|
|
12
|
+
if (o === s) return !0;
|
|
13
|
+
if (!a.acceptPartial) return !1;
|
|
14
|
+
let c = t(s);
|
|
15
|
+
return c > 0 && r(o, s, c) <= c;
|
|
16
|
+
}
|
|
17
|
+
function r(e, t, n = Infinity) {
|
|
18
|
+
let r = [...e], i = [...t];
|
|
19
|
+
if (Math.abs(r.length - i.length) > n) return n + 1;
|
|
20
|
+
let a = [], o = Array.from({ length: i.length + 1 }, (e, t) => t);
|
|
21
|
+
for (let e = 1; e <= r.length; e++) {
|
|
22
|
+
let t = [e], s = e;
|
|
23
|
+
for (let n = 1; n <= i.length; n++) {
|
|
24
|
+
let c = r[e - 1] === i[n - 1] ? 0 : 1, l = Math.min(o[n] + 1, t[n - 1] + 1, o[n - 1] + c);
|
|
25
|
+
e > 1 && n > 1 && r[e - 1] === i[n - 2] && r[e - 2] === i[n - 1] && (l = Math.min(l, a[n - 2] + 1)), t[n] = l, s = Math.min(s, l);
|
|
26
|
+
}
|
|
27
|
+
if (s > n) return n + 1;
|
|
28
|
+
a = o, o = t;
|
|
29
|
+
}
|
|
30
|
+
return o[i.length];
|
|
31
|
+
}
|
|
32
|
+
var i = /^(-?)(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(Z|[+-]\d{2}:\d{2})?)?$/, a = {
|
|
33
|
+
year: 0,
|
|
34
|
+
month: 1,
|
|
35
|
+
day: 2,
|
|
36
|
+
datetime: 3
|
|
37
|
+
};
|
|
38
|
+
function o(e) {
|
|
39
|
+
let t = i.exec(e);
|
|
40
|
+
if (!t) return null;
|
|
41
|
+
let n = (t[1] ? -1 : 1) * Number(t[2]);
|
|
42
|
+
if (n === 0) return null;
|
|
43
|
+
let r = Number(t[3]), a = s(n < 0 ? n + 1 : n, r, Number(t[4])), o = t[5] !== void 0, c = 0;
|
|
44
|
+
if (o) {
|
|
45
|
+
c = ((Number(t[5]) * 60 + Number(t[6])) * 60 + Number(t[7])) * 1e3 + Number((t[8] ?? "0").padEnd(3, "0"));
|
|
46
|
+
let e = t[9];
|
|
47
|
+
if (e && e !== "Z") {
|
|
48
|
+
let t = e[0] === "-" ? -1 : 1;
|
|
49
|
+
c -= t * (Number(e.slice(1, 3)) * 60 + Number(e.slice(4, 6))) * 6e4;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
year: n,
|
|
54
|
+
month: r,
|
|
55
|
+
days: a,
|
|
56
|
+
instant: a * 864e5 + c,
|
|
57
|
+
hasTime: o
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function s(e, t, n) {
|
|
61
|
+
let r = t <= 2 ? e - 1 : e, i = Math.floor(r / 400), a = r - i * 400, o = Math.floor((153 * (t + (t > 2 ? -3 : 9)) + 2) / 5) + n - 1, s = a * 365 + Math.floor(a / 4) - Math.floor(a / 100) + o;
|
|
62
|
+
return i * 146097 + s - 719468;
|
|
63
|
+
}
|
|
64
|
+
function c(e) {
|
|
65
|
+
return o(e) !== null;
|
|
66
|
+
}
|
|
67
|
+
function l(e, t) {
|
|
68
|
+
let n = o(e.date), r = o(t.date);
|
|
69
|
+
if (!n || !r) throw Error(`Invalid timeline date: ${n ? t.date : e.date}`);
|
|
70
|
+
let i = (e, t) => a[t ?? (e.hasTime ? "datetime" : "day")], s = Math.min(i(n, e.precision), i(r, t.precision));
|
|
71
|
+
return s === a.year ? Math.sign(n.year - r.year) : s === a.month ? Math.sign(n.year - r.year || n.month - r.month) : s === a.day ? Math.sign(n.days - r.days) : Math.sign(n.instant - r.instant);
|
|
72
|
+
}
|
|
73
|
+
function u(e) {
|
|
74
|
+
for (let t = 0; t < e.length; t++) for (let n = t + 1; n < e.length; n++) if (l(e[t], e[n]) > 0) return !1;
|
|
75
|
+
return !0;
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { t as allowedTypos, l as compareTimelineEvents, r as editDistance, c as isTimelineDate, u as isTimelineOrderCorrect, e as normalizeAnswerText, n as textAnswerMatches };
|
package/dist/assets.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e,t,n){let r=e.toLowerCase();return t?.assets?.[r]??n?.assets?.[r]}exports.resolveAsset=e;
|
package/dist/assets.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=require("./answers.cjs");function t(t,p){if(!t||typeof t!=`object`)throw Error(`checkAnswer: missing item`);switch(t.type){case`mcq-single`:return p===t.correctId;case`mcq-multi`:{let e=i(p);return e!==null&&c(e,t.correctIds)}case`true-false`:return r(p)===t.correctAnswer;case`short-answer`:{let n=p;return typeof n==`string`&&t.correctAnswers.some(r=>e.textAnswerMatches(n,r,t))}case`numeric-input`:{let e=n(p);return e!==null&&Math.abs(e-t.correctAnswer)<=(t.tolerance??0)+1e-9}case`slider`:{let e=n(p);return e!==null&&Math.abs(e-t.correctAnswer)<=(t.tolerance??0)+1e-9}case`math-input`:{if(typeof p!=`string`)return!1;let e=l(p);return[t.correctAnswer,...t.alternativeAnswers??[]].some(t=>l(t)===e)}case`sort-items`:{let e=i(p);return e!==null&&e.length===t.correctOrder.length&&e.every((e,n)=>e===t.correctOrder[n])}case`match-pairs`:{let e=s(p);return!!e&&t.prompts.every(n=>e[n.id]===t.pairs[n.id])}case`match-complex`:{let e=a(p);if(!e)return!1;let n=new Set(t.connections.map(([e,t])=>o(e,t))),r=new Set(e.map(([e,t])=>o(e,t))),i=[...r].filter(e=>n.has(e)).length;return i===r.size&&i>=(t.minCorrect??n.size)}case`matrix`:{let e=a(p);return e?c(e.map(([e,t])=>o(e,t)),t.correctCells.map(([e,t])=>o(e,t))):!1}case`fill-in-blanks`:{let n=s(p);return n?Object.entries(t.blanks).every(([r,i])=>{let a=n[r];return typeof a==`string`&&i.some(n=>e.textAnswerMatches(a,n,t))}):!1}case`fill-in-select`:{let e=s(p);return!!e&&Object.entries(t.blanks).every(([t,n])=>e[t]===n.correctId)}case`categorize`:{let e=s(p);return!!e&&t.items.every(t=>e[t.id]===t.correctCategoryId)}case`timeline`:{let n=i(p);if(!n||n.length!==t.events.length||new Set(n).size!==n.length)return!1;let r=n.map(e=>t.events.find(t=>t.id===e));return r.every(e=>!!e)&&e.isTimelineOrderCorrect(r)}case`pin-on-image`:{let e=(Array.isArray(p)?p:[p]).map(d);if(e.length===0||e.some(e=>e===null))return!1;if(!t.multipleCorrect)return e.length===1&&t.hotspots.some(t=>f(e[0],t));let n=new Set;for(let r of e){let e=t.hotspots.findIndex(e=>f(r,e));if(e===-1)return!1;n.add(e)}return n.size>=(t.minCorrect??t.hotspots.length)}case`diagram-label`:{let n=s(p);return n?t.zones.every(r=>{let i=n[r.id];if(t.requireTyping){let n=t.labels.find(e=>e.id===r.correctLabelId);return!!n&&typeof i==`string`&&e.textAnswerMatches(i,n.text,t)}return i===r.correctLabelId}):!1}case`pin-on-model`:{let e=(Array.isArray(p)?p:[p]).filter(e=>typeof e==`string`);if(e.length===0)return!1;let n=e=>t.hotspots.findIndex(t=>e.toLowerCase().includes(t.targetName.toLowerCase()));if(!t.multipleCorrect)return e.length===1&&n(e[0])!==-1;let r=new Set;for(let t of e){let e=n(t);if(e===-1)return!1;r.add(e)}return r.size>=(t.minCorrect??t.hotspots.length)}case`chess-puzzle`:{if(!Array.isArray(p)||p.some(e=>typeof e!=`string`))return!1;let e=p.map(u);return t.correctAnswers.some(t=>t.length===e.length&&t.every((t,n)=>u(t)===e[n]))}case`flashcard`:{let e=n(p);return e===3||e===4}case`note`:case`open-ended`:throw Error(`checkAnswer: "${t.type}" items cannot be checked automatically.`);default:throw Error(`checkAnswer: unsupported item type "${t.type}".`)}}function n(e){if(typeof e==`number`)return Number.isFinite(e)?e:null;if(typeof e!=`string`)return null;let t=e.trim().replace(/\s+/g,``).replace(`,`,`.`);return/^[-+]?(\d+\.?\d*|\.\d+)(e[-+]?\d+)?$/i.test(t)?Number(t):null}function r(e){return typeof e==`boolean`?e:e===`true`||e!==`false`&&null}function i(e){return Array.isArray(e)&&e.every(e=>typeof e==`string`)?e:null}function a(e){return Array.isArray(e)&&e.every(e=>Array.isArray(e)&&e.length===2&&typeof e[0]==`string`&&typeof e[1]==`string`)?e:null}var o=(e,t)=>`${e}\u0000${t}`;function s(e){return e&&typeof e==`object`&&!Array.isArray(e)?e:null}function c(e,t){let n=new Set(e),r=new Set(t);return n.size===e.length&&n.size===r.size&&[...n].every(e=>r.has(e))}function l(e){return e.trim().replace(/^\$\$?|\$\$?$/g,``).replace(/\s+/g,``)}function u(e){return e.trim().replace(/[+#!?]+$/g,``).replace(/^0-0(-0)?$/,e=>e.replace(/0/g,`O`)).replace(/^([a-h](?:x[a-h])?[18])([QRBN])$/,`$1=$2`)}function d(e){let t=s(e);return t&&typeof t.x==`number`&&typeof t.y==`number`?{x:t.x,y:t.y}:null}function f(e,t){switch(t.type){case`rect`:return e.x>=t.x&&e.x<=t.x+t.width&&e.y>=t.y&&e.y<=t.y+t.height;case`circle`:return(e.x-t.x)**2+(e.y-t.y)**2<=t.radius**2;case`polygon`:{let n=!1,r=t.points;for(let t=0,i=r.length-1;t<r.length;i=t++)r[t].y>e.y!=r[i].y>e.y&&e.x<(r[i].x-r[t].x)*(e.y-r[t].y)/(r[i].y-r[t].y)+r[t].x&&(n=!n);return n}}}exports.checkAnswer=t;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { isTimelineOrderCorrect as e, textAnswerMatches as t } from "./answers.js";
|
|
2
|
+
//#region src/checkAnswer.ts
|
|
3
|
+
function n(n, m) {
|
|
4
|
+
if (!n || typeof n != "object") throw Error("checkAnswer: missing item");
|
|
5
|
+
switch (n.type) {
|
|
6
|
+
case "mcq-single": return m === n.correctId;
|
|
7
|
+
case "mcq-multi": {
|
|
8
|
+
let e = a(m);
|
|
9
|
+
return e !== null && l(e, n.correctIds);
|
|
10
|
+
}
|
|
11
|
+
case "true-false": return i(m) === n.correctAnswer;
|
|
12
|
+
case "short-answer": {
|
|
13
|
+
let e = m;
|
|
14
|
+
return typeof e == "string" && n.correctAnswers.some((r) => t(e, r, n));
|
|
15
|
+
}
|
|
16
|
+
case "numeric-input": {
|
|
17
|
+
let e = r(m);
|
|
18
|
+
return e !== null && Math.abs(e - n.correctAnswer) <= (n.tolerance ?? 0) + 1e-9;
|
|
19
|
+
}
|
|
20
|
+
case "slider": {
|
|
21
|
+
let e = r(m);
|
|
22
|
+
return e !== null && Math.abs(e - n.correctAnswer) <= (n.tolerance ?? 0) + 1e-9;
|
|
23
|
+
}
|
|
24
|
+
case "math-input": {
|
|
25
|
+
if (typeof m != "string") return !1;
|
|
26
|
+
let e = u(m);
|
|
27
|
+
return [n.correctAnswer, ...n.alternativeAnswers ?? []].some((t) => u(t) === e);
|
|
28
|
+
}
|
|
29
|
+
case "sort-items": {
|
|
30
|
+
let e = a(m);
|
|
31
|
+
return e !== null && e.length === n.correctOrder.length && e.every((e, t) => e === n.correctOrder[t]);
|
|
32
|
+
}
|
|
33
|
+
case "match-pairs": {
|
|
34
|
+
let e = c(m);
|
|
35
|
+
return !!e && n.prompts.every((t) => e[t.id] === n.pairs[t.id]);
|
|
36
|
+
}
|
|
37
|
+
case "match-complex": {
|
|
38
|
+
let e = o(m);
|
|
39
|
+
if (!e) return !1;
|
|
40
|
+
let t = new Set(n.connections.map(([e, t]) => s(e, t))), r = new Set(e.map(([e, t]) => s(e, t))), i = [...r].filter((e) => t.has(e)).length;
|
|
41
|
+
return i === r.size && i >= (n.minCorrect ?? t.size);
|
|
42
|
+
}
|
|
43
|
+
case "matrix": {
|
|
44
|
+
let e = o(m);
|
|
45
|
+
return e ? l(e.map(([e, t]) => s(e, t)), n.correctCells.map(([e, t]) => s(e, t))) : !1;
|
|
46
|
+
}
|
|
47
|
+
case "fill-in-blanks": {
|
|
48
|
+
let e = c(m);
|
|
49
|
+
return e ? Object.entries(n.blanks).every(([r, i]) => {
|
|
50
|
+
let a = e[r];
|
|
51
|
+
return typeof a == "string" && i.some((e) => t(a, e, n));
|
|
52
|
+
}) : !1;
|
|
53
|
+
}
|
|
54
|
+
case "fill-in-select": {
|
|
55
|
+
let e = c(m);
|
|
56
|
+
return !!e && Object.entries(n.blanks).every(([t, n]) => e[t] === n.correctId);
|
|
57
|
+
}
|
|
58
|
+
case "categorize": {
|
|
59
|
+
let e = c(m);
|
|
60
|
+
return !!e && n.items.every((t) => e[t.id] === t.correctCategoryId);
|
|
61
|
+
}
|
|
62
|
+
case "timeline": {
|
|
63
|
+
let t = a(m);
|
|
64
|
+
if (!t || t.length !== n.events.length || new Set(t).size !== t.length) return !1;
|
|
65
|
+
let r = t.map((e) => n.events.find((t) => t.id === e));
|
|
66
|
+
return r.every((e) => !!e) && e(r);
|
|
67
|
+
}
|
|
68
|
+
case "pin-on-image": {
|
|
69
|
+
let e = (Array.isArray(m) ? m : [m]).map(f);
|
|
70
|
+
if (e.length === 0 || e.some((e) => e === null)) return !1;
|
|
71
|
+
if (!n.multipleCorrect) return e.length === 1 && n.hotspots.some((t) => p(e[0], t));
|
|
72
|
+
let t = /* @__PURE__ */ new Set();
|
|
73
|
+
for (let r of e) {
|
|
74
|
+
let e = n.hotspots.findIndex((e) => p(r, e));
|
|
75
|
+
if (e === -1) return !1;
|
|
76
|
+
t.add(e);
|
|
77
|
+
}
|
|
78
|
+
return t.size >= (n.minCorrect ?? n.hotspots.length);
|
|
79
|
+
}
|
|
80
|
+
case "diagram-label": {
|
|
81
|
+
let e = c(m);
|
|
82
|
+
return e ? n.zones.every((r) => {
|
|
83
|
+
let i = e[r.id];
|
|
84
|
+
if (n.requireTyping) {
|
|
85
|
+
let e = n.labels.find((e) => e.id === r.correctLabelId);
|
|
86
|
+
return !!e && typeof i == "string" && t(i, e.text, n);
|
|
87
|
+
}
|
|
88
|
+
return i === r.correctLabelId;
|
|
89
|
+
}) : !1;
|
|
90
|
+
}
|
|
91
|
+
case "pin-on-model": {
|
|
92
|
+
let e = (Array.isArray(m) ? m : [m]).filter((e) => typeof e == "string");
|
|
93
|
+
if (e.length === 0) return !1;
|
|
94
|
+
let t = (e) => n.hotspots.findIndex((t) => e.toLowerCase().includes(t.targetName.toLowerCase()));
|
|
95
|
+
if (!n.multipleCorrect) return e.length === 1 && t(e[0]) !== -1;
|
|
96
|
+
let r = /* @__PURE__ */ new Set();
|
|
97
|
+
for (let n of e) {
|
|
98
|
+
let e = t(n);
|
|
99
|
+
if (e === -1) return !1;
|
|
100
|
+
r.add(e);
|
|
101
|
+
}
|
|
102
|
+
return r.size >= (n.minCorrect ?? n.hotspots.length);
|
|
103
|
+
}
|
|
104
|
+
case "chess-puzzle": {
|
|
105
|
+
if (!Array.isArray(m) || m.some((e) => typeof e != "string")) return !1;
|
|
106
|
+
let e = m.map(d);
|
|
107
|
+
return n.correctAnswers.some((t) => t.length === e.length && t.every((t, n) => d(t) === e[n]));
|
|
108
|
+
}
|
|
109
|
+
case "flashcard": {
|
|
110
|
+
let e = r(m);
|
|
111
|
+
return e === 3 || e === 4;
|
|
112
|
+
}
|
|
113
|
+
case "note":
|
|
114
|
+
case "open-ended": throw Error(`checkAnswer: "${n.type}" items cannot be checked automatically.`);
|
|
115
|
+
default: throw Error(`checkAnswer: unsupported item type "${n.type}".`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function r(e) {
|
|
119
|
+
if (typeof e == "number") return Number.isFinite(e) ? e : null;
|
|
120
|
+
if (typeof e != "string") return null;
|
|
121
|
+
let t = e.trim().replace(/\s+/g, "").replace(",", ".");
|
|
122
|
+
return /^[-+]?(\d+\.?\d*|\.\d+)(e[-+]?\d+)?$/i.test(t) ? Number(t) : null;
|
|
123
|
+
}
|
|
124
|
+
function i(e) {
|
|
125
|
+
return typeof e == "boolean" ? e : e === "true" || e !== "false" && null;
|
|
126
|
+
}
|
|
127
|
+
function a(e) {
|
|
128
|
+
return Array.isArray(e) && e.every((e) => typeof e == "string") ? e : null;
|
|
129
|
+
}
|
|
130
|
+
function o(e) {
|
|
131
|
+
return Array.isArray(e) && e.every((e) => Array.isArray(e) && e.length === 2 && typeof e[0] == "string" && typeof e[1] == "string") ? e : null;
|
|
132
|
+
}
|
|
133
|
+
var s = (e, t) => `${e}\u0000${t}`;
|
|
134
|
+
function c(e) {
|
|
135
|
+
return e && typeof e == "object" && !Array.isArray(e) ? e : null;
|
|
136
|
+
}
|
|
137
|
+
function l(e, t) {
|
|
138
|
+
let n = new Set(e), r = new Set(t);
|
|
139
|
+
return n.size === e.length && n.size === r.size && [...n].every((e) => r.has(e));
|
|
140
|
+
}
|
|
141
|
+
function u(e) {
|
|
142
|
+
return e.trim().replace(/^\$\$?|\$\$?$/g, "").replace(/\s+/g, "");
|
|
143
|
+
}
|
|
144
|
+
function d(e) {
|
|
145
|
+
return e.trim().replace(/[+#!?]+$/g, "").replace(/^0-0(-0)?$/, (e) => e.replace(/0/g, "O")).replace(/^([a-h](?:x[a-h])?[18])([QRBN])$/, "$1=$2");
|
|
146
|
+
}
|
|
147
|
+
function f(e) {
|
|
148
|
+
let t = c(e);
|
|
149
|
+
return t && typeof t.x == "number" && typeof t.y == "number" ? {
|
|
150
|
+
x: t.x,
|
|
151
|
+
y: t.y
|
|
152
|
+
} : null;
|
|
153
|
+
}
|
|
154
|
+
function p(e, t) {
|
|
155
|
+
switch (t.type) {
|
|
156
|
+
case "rect": return e.x >= t.x && e.x <= t.x + t.width && e.y >= t.y && e.y <= t.y + t.height;
|
|
157
|
+
case "circle": return (e.x - t.x) ** 2 + (e.y - t.y) ** 2 <= t.radius ** 2;
|
|
158
|
+
case "polygon": {
|
|
159
|
+
let n = !1, r = t.points;
|
|
160
|
+
for (let t = 0, i = r.length - 1; t < r.length; i = t++) r[t].y > e.y != r[i].y > e.y && e.x < (r[i].x - r[t].x) * (e.y - r[t].y) / (r[i].y - r[t].y) + r[t].x && (n = !n);
|
|
161
|
+
return n;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
export { n as checkAnswer };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=require("./manifest.cjs"),t=require("./features.cjs");function n(n,r){let i=e.withManifestDefaults(r).capabilities,a=(e,t)=>!!e&&e.length>0&&(e.includes(`*`)||e.includes(t)),o=[...new Set(n.items.map(e=>e.type))].filter(e=>!a(i.types,e)),s=[],c=[],l=(e,t)=>{for(let[n,r]of Object.entries(e??{})){let e=i.assets?.[r.type],a={path:`${t}.${n}`,type:r.type,mimeType:r.mimeType};if(!e||e.length===0)s.push(a);else if(e.includes(`*`))continue;else r.mimeType?e.includes(r.mimeType)||s.push(a):c.push(a)}};l(n.meta.assets,`meta.assets`),n.items.forEach((e,t)=>l(e.assets,`items[${t}].assets`));let u=n.meta.requirements,d=(e,t)=>(e??[]).filter(e=>!(t??[]).includes(e)),f=d(t.effectiveFeatures(n),i.features),p=d(u?.latexPackages,i.latexPackages),m=[...d(u?.itemProperties,i.itemProperties),...d(u?.metaProperties,i.metaProperties)],h=e.isVersionCompatible(n.version,r),g=h&&o.length===0&&s.length===0;return{level:g?f.length>0||p.length>0?`simplified`:`full`:`incompatible`,compatible:g,versionCompatible:h,unsupportedTypes:o,unsupportedAssets:s,unverifiedAssets:c,missingFeatures:f,missingLatexPackages:p,missingProperties:m}}exports.checkCompatibility=n;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { isVersionCompatible as e, withManifestDefaults as t } from "./manifest.js";
|
|
2
|
+
import { effectiveFeatures as n } from "./features.js";
|
|
3
|
+
//#region src/compatibility.ts
|
|
4
|
+
function r(r, i) {
|
|
5
|
+
let a = t(i).capabilities, o = (e, t) => !!e && e.length > 0 && (e.includes("*") || e.includes(t)), s = [...new Set(r.items.map((e) => e.type))].filter((e) => !o(a.types, e)), c = [], l = [], u = (e, t) => {
|
|
6
|
+
for (let [n, r] of Object.entries(e ?? {})) {
|
|
7
|
+
let e = a.assets?.[r.type], i = {
|
|
8
|
+
path: `${t}.${n}`,
|
|
9
|
+
type: r.type,
|
|
10
|
+
mimeType: r.mimeType
|
|
11
|
+
};
|
|
12
|
+
if (!e || e.length === 0) c.push(i);
|
|
13
|
+
else if (e.includes("*")) continue;
|
|
14
|
+
else r.mimeType ? e.includes(r.mimeType) || c.push(i) : l.push(i);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
u(r.meta.assets, "meta.assets"), r.items.forEach((e, t) => u(e.assets, `items[${t}].assets`));
|
|
18
|
+
let d = r.meta.requirements, f = (e, t) => (e ?? []).filter((e) => !(t ?? []).includes(e)), p = f(n(r), a.features), m = f(d?.latexPackages, a.latexPackages), h = [...f(d?.itemProperties, a.itemProperties), ...f(d?.metaProperties, a.metaProperties)], g = e(r.version, i), _ = g && s.length === 0 && c.length === 0;
|
|
19
|
+
return {
|
|
20
|
+
level: _ ? p.length > 0 || m.length > 0 ? "simplified" : "full" : "incompatible",
|
|
21
|
+
compatible: _,
|
|
22
|
+
versionCompatible: g,
|
|
23
|
+
unsupportedTypes: s,
|
|
24
|
+
unsupportedAssets: c,
|
|
25
|
+
unverifiedAssets: l,
|
|
26
|
+
missingFeatures: p,
|
|
27
|
+
missingLatexPackages: m,
|
|
28
|
+
missingProperties: h
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { r as checkCompatibility };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=require("./manifest.cjs"),t=require("./richTextProcessor.cjs"),n=require("./richTextFields.cjs");var r=[`ar`,`he`,`fa`,`ur`,`yi`,`ps`,`sd`,`dv`,`ug`],i={mermaid:`mermaid`,smiles:`smiles`,abc:`abc-notation`},a=/^ {0,3}(?:`{3,}|~{3,})[ \t]*([^\s`~]*)/gm,o=/^ {0,3}(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:^ {0,3}\1[`~]*[ \t]*$|(?![\s\S]))/gm,s=/\$\$[\s\S]*?\$\$|\$[^$\n]+\$/g,c=[/^ {0,3}#{1,6}[ \t]+\S/m,/(\*\*|__)(?=\S)[^\n]*?\S\1/,/(^|[^\w*])\*(?=[^\s*])[^*\n]*?[^\s*]\*(?![\w*])/m,/(^|[^\w])_(?=[^\s_])[^_\n]*?[^\s_]_(?!\w)/m,/~~(?=\S)[^\n]*?\S~~/,/^[ \t]*(?:[-*+]|\d{1,9}[.)])[ \t]+\S/m,/^ {0,3}>/m,/!?\[[^\]\n]*\]\([^)\n]*\)/,/`[^`\n]+`/,/^[ \t]*\|?[ \t]*:?-{3,}:?[ \t]*\|/m,/^ {0,3}([-*_])([ \t]*\1){2,}[ \t]*$/m],l=/\$\$[^$]*[\\^_{}][^$]*\$\$|\$[^$\n]*[\\^_{}][^$\n]*\$/;function u(e){return e?.split(/[-_]/)[0]?.toLowerCase()}function d(e,n,r){for(let t of e.matchAll(a)){r.add(`markdown`);let e=t[1].toLowerCase();e&&r.add(i[e]??`syntax-highlighting`)}t.findRawHtml(e,{latex:n})&&r.add(`html`);let l=e.replace(o,``);n&&(l=l.replace(s,``)),c.some(e=>e.test(l))&&r.add(`markdown`)}function f(t){let i=t.meta.requirements?.features?.includes(`latex`)??!1,a=new Set;for(let[,e]of n.metaRichText(t.meta))d(e,i,a);for(let e of t.items)for(let[,t]of n.itemRichText(e))d(t,i,a);return[t.meta.language,...t.items.map(e=>e.lang)].some(e=>r.includes(u(e)??``))&&a.add(`rtl`),e.OFFICIAL_FEATURE_KEYS.filter(e=>a.has(e))}function p(e){return[...new Set([...e.meta.requirements?.features??[],...f(e)])]}function m(e){return l.test(e.replace(o,``).replace(/`[^`\n]+`/g,``))}exports.RTL_LANGUAGES=r,exports.detectFeatures=f,exports.effectiveFeatures=p,exports.looksLikeLatex=m;
|
package/dist/features.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { OFFICIAL_FEATURE_KEYS as e } from "./manifest.js";
|
|
2
|
+
import { findRawHtml as t } from "./richTextProcessor.js";
|
|
3
|
+
import { itemRichText as n, metaRichText as r } from "./richTextFields.js";
|
|
4
|
+
//#region src/features.ts
|
|
5
|
+
var i = [
|
|
6
|
+
"ar",
|
|
7
|
+
"he",
|
|
8
|
+
"fa",
|
|
9
|
+
"ur",
|
|
10
|
+
"yi",
|
|
11
|
+
"ps",
|
|
12
|
+
"sd",
|
|
13
|
+
"dv",
|
|
14
|
+
"ug"
|
|
15
|
+
], a = {
|
|
16
|
+
mermaid: "mermaid",
|
|
17
|
+
smiles: "smiles",
|
|
18
|
+
abc: "abc-notation"
|
|
19
|
+
}, o = /^ {0,3}(?:`{3,}|~{3,})[ \t]*([^\s`~]*)/gm, s = /^ {0,3}(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:^ {0,3}\1[`~]*[ \t]*$|(?![\s\S]))/gm, c = /\$\$[\s\S]*?\$\$|\$[^$\n]+\$/g, l = [
|
|
20
|
+
/^ {0,3}#{1,6}[ \t]+\S/m,
|
|
21
|
+
/(\*\*|__)(?=\S)[^\n]*?\S\1/,
|
|
22
|
+
/(^|[^\w*])\*(?=[^\s*])[^*\n]*?[^\s*]\*(?![\w*])/m,
|
|
23
|
+
/(^|[^\w])_(?=[^\s_])[^_\n]*?[^\s_]_(?!\w)/m,
|
|
24
|
+
/~~(?=\S)[^\n]*?\S~~/,
|
|
25
|
+
/^[ \t]*(?:[-*+]|\d{1,9}[.)])[ \t]+\S/m,
|
|
26
|
+
/^ {0,3}>/m,
|
|
27
|
+
/!?\[[^\]\n]*\]\([^)\n]*\)/,
|
|
28
|
+
/`[^`\n]+`/,
|
|
29
|
+
/^[ \t]*\|?[ \t]*:?-{3,}:?[ \t]*\|/m,
|
|
30
|
+
/^ {0,3}([-*_])([ \t]*\1){2,}[ \t]*$/m
|
|
31
|
+
], u = /\$\$[^$]*[\\^_{}][^$]*\$\$|\$[^$\n]*[\\^_{}][^$\n]*\$/;
|
|
32
|
+
function d(e) {
|
|
33
|
+
return e?.split(/[-_]/)[0]?.toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
function f(e, n, r) {
|
|
36
|
+
for (let t of e.matchAll(o)) {
|
|
37
|
+
r.add("markdown");
|
|
38
|
+
let e = t[1].toLowerCase();
|
|
39
|
+
e && r.add(a[e] ?? "syntax-highlighting");
|
|
40
|
+
}
|
|
41
|
+
t(e, { latex: n }) && r.add("html");
|
|
42
|
+
let i = e.replace(s, "");
|
|
43
|
+
n && (i = i.replace(c, "")), l.some((e) => e.test(i)) && r.add("markdown");
|
|
44
|
+
}
|
|
45
|
+
function p(t) {
|
|
46
|
+
let a = t.meta.requirements?.features?.includes("latex") ?? !1, o = /* @__PURE__ */ new Set();
|
|
47
|
+
for (let [, e] of r(t.meta)) f(e, a, o);
|
|
48
|
+
for (let e of t.items) for (let [, t] of n(e)) f(t, a, o);
|
|
49
|
+
return [t.meta.language, ...t.items.map((e) => e.lang)].some((e) => i.includes(d(e) ?? "")) && o.add("rtl"), e.filter((e) => o.has(e));
|
|
50
|
+
}
|
|
51
|
+
function m(e) {
|
|
52
|
+
return [.../* @__PURE__ */ new Set([...e.meta.requirements?.features ?? [], ...p(e)])];
|
|
53
|
+
}
|
|
54
|
+
function h(e) {
|
|
55
|
+
return u.test(e.replace(s, "").replace(/`[^`\n]+`/g, ""));
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { i as RTL_LANGUAGES, p as detectFeatures, m as effectiveFeatures, h as looksLikeLatex };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./oqse.cjs"),t=require("./utils.cjs"),n=require("./oqseValidation.cjs"),r=require("./manifest.cjs"),i=require("./markdownUtils.cjs"),a=require("./richTextProcessor.cjs"),o=require("./assets.cjs"),s=require("./richTextFields.cjs"),c=require("./features.cjs");var l=class extends Error{errors;warnings;constructor(e,t){super(`Invalid OQSE file:\n${u(e).join(`
|
|
2
|
+
`)}`),this.name=`OQSEValidationError`,this.errors=e,this.warnings=t}};function u(e){return e.map(e=>e.path?`${e.path}: ${e.message}`:e.message)}var d=1,f=`1.0`;function p(e){return v(e,`load`)}function m(e){let t=v(e,`strict`),n=t.success&&t.errors.length===0;return{success:n,data:n?t.data:void 0,errors:t.errors,warnings:t.warnings}}function h(e){let t=m(e);if(!t.success)throw new l(t.errors,t.warnings);return t.data}var g=new Set(e.OFFICIAL_ITEM_TYPES),_=class{errors=[];warnings=[];add(e,n,r,i,a){let o={severity:e,code:n,message:r,path:t.formatPath(i)};a&&(o.itemId=a),(e===`error`?this.errors:this.warnings).push(o)}addZod(e,t,n,r){for(let i of e.issues)this.add(`error`,t,i.message,[...n,...i.path],r)}};function v(e,i){let o=new _,l=()=>({success:!1,errors:o.errors,warnings:o.warnings}),u=i===`strict`?`error`:`warning`;try{t.validateJsonDepth(e,10)}catch(e){return o.add(`error`,`SECURITY_LIMIT`,e.message,[]),l()}if(!w(e)||!w(e.meta)||!Array.isArray(e.items))return o.add(`error`,`INVALID_ROOT`,`An OQSE file must be an object with "version", "meta" and "items" (array).`,[]),l();let d=structuredClone(e);if(d.$schema!==void 0&&typeof d.$schema!=`string`)return o.add(`error`,`INVALID_ROOT`,`"$schema" must be a string URL.`,[`$schema`]),l();if(typeof d.version!=`string`||!/^\d+\.\d+$/.test(d.version))return o.add(`error`,`INVALID_VERSION`,`Version must be in MAJOR.MINOR format (e.g. "1.0").`,[`version`]),l();let f=Number(d.version.split(`.`)[0]);if(f===0)return o.add(`error`,`UNSUPPORTED_VERSION`,`OQSE ${d.version} is a pre-release version; this library reads OQSE 1.x (convert the set to 1.0).`,[`version`]),l();if(f!==1&&o.add(`warning`,`UNSUPPORTED_VERSION`,`Unsupported MAJOR version ${d.version}; loading in best-effort mode.`,[`version`]),d.items.length>1e4)return o.add(`error`,`TOO_MANY_ITEMS`,`Maximum 10000 items per set.`,[`items`]),l();if(i===`load`&&!C(d.meta,[`meta`,`assets`],o))return l();let p=n.OQSEMetaSchema.safeParse(d.meta);if(!p.success)return o.addZod(p.error,`INVALID_META`,[`meta`]),l();let m=p.data,h={features:r.OFFICIAL_FEATURE_KEYS,itemProperties:r.OFFICIAL_ITEM_PROPERTIES,metaProperties:r.OFFICIAL_META_PROPERTIES};for(let[e,t]of Object.entries(h))for(let n of m.requirements?.[e]??[])!n.startsWith(`x-`)&&!t.includes(n)&&o.add(`warning`,`UNKNOWN_FEATURE`,`Unknown ${e===`features`?`feature`:`property`} "${n}" in requirements.${e} (ignored).`,[`meta`,`requirements`,e]);let v=new Set(m.requirements?.features??[]),x=v.has(`html`),S=v.has(`latex`);b(m.assets,[`meta`,`assets`],o,i),m.thumbnail&&!m.assets?.[m.thumbnail]&&(o.add(u,`MISSING_THUMBNAIL_ASSET`,`Thumbnail references non-existent asset "${m.thumbnail}".`,[`meta`,`thumbnail`]),i===`load`&&delete m.thumbnail);for(let[e,t]of s.metaRichText(m)){let n=x?null:a.findRawHtml(t,{latex:S});n&&(o.add(u,`RAW_HTML_NOT_ALLOWED`,T(n),e),i===`load`&&E(m,e.slice(1)))}let D=new Set([m.id]),O=[];d.items.forEach((e,t)=>{let r=[`items`,t],a=w(e)&&typeof e.id==`string`?e.id:void 0,s=w(e)?e.type:void 0;if(typeof s==`string`&&!s.startsWith(`x-`)&&!g.has(s)){o.add(u,`UNKNOWN_ITEM_TYPE`,`Unknown item type "${s}" (custom types must use the "x-" prefix).`,[...r,`type`],a);return}if(i===`load`&&w(e)&&!C(e,[...r,`assets`],o,a))return;let c=n.getItemSchema(e).safeParse(e);if(!c.success){o.addZod(c.error,`INVALID_ITEM`,r,a);return}let l=c.data;if(D.has(l.id)){o.add(`error`,`DUPLICATE_ID`,`Duplicate ID "${l.id}" (IDs must be unique within the set, including meta.id).`,[...r,`id`],l.id);return}D.add(l.id),b(l.assets,[...r,`assets`],o,i,l.id),y(l,r,m,x,S,o,u)&&O.push({item:l,index:t})});let k=O.map(e=>e.item),A=new Set(k.map(e=>e.id)),j=new Set((m.sourceMaterials??[]).map(e=>e.id));for(let{item:e,index:t}of O){let n=[`items`,t];for(let t of[`relatedItems`,`dependencyItems`]){let r=e[t];if(!r)continue;let a=r.filter(t=>t!==e.id&&A.has(t));a.length!==r.length&&(o.add(u,`DANGLING_REFERENCE`,`${t} contains IDs that do not exist in the set or reference the item itself.`,[...n,t],e.id),i===`load`&&(e[t]=a))}if(e.sources){let t=e.sources.filter(e=>j.has(e.id));t.length!==e.sources.length&&(o.add(u,`DANGLING_REFERENCE`,`sources reference source materials that do not exist in meta.sourceMaterials.`,[...n,`sources`],e.id),i===`load`&&(e.sources=t))}}for(let e of c.detectFeatures({meta:m,items:k}))e!==`html`&&!v.has(e)&&o.add(`warning`,`UNDECLARED_FEATURE`,`The content uses "${e}", but meta.requirements.features does not declare it.`,[`meta`,`requirements`,`features`]);if(!S)for(let{item:e,index:t}of O){let n=s.itemRichText(e).find(([,e])=>c.looksLikeLatex(e));n&&o.add(`warning`,`LOOKS_LIKE_LATEX`,`The text looks like a LaTeX formula; if it is one, declare the "latex" feature (otherwise $ is an ordinary character).`,[`items`,t,...n[0]],e.id)}return{success:!0,data:{...d,meta:m,items:k},errors:o.errors,warnings:o.warnings}}function y(e,t,n,r,c,l,u){let d=!0;if(!r)for(let[n,r]of s.itemRichText(e)){let i=a.findRawHtml(r,{latex:c});i&&(l.add(`error`,`RAW_HTML_NOT_ALLOWED`,T(i),[...t,...n],e.id),d=!1)}if(`targetAsset`in e&&typeof e.targetAsset==`string`&&!e.type.startsWith(`x-`)){let r=o.resolveAsset(e.targetAsset,e,n),i=e.type===`pin-on-model`?`model`:`image`;r?r.type!==i&&(l.add(`error`,`INVALID_TARGET_ASSET`,`targetAsset "${e.targetAsset}" must be of type "${i}".`,[...t,`targetAsset`],e.id),d=!1):(l.add(`error`,`MISSING_TARGET_ASSET`,`targetAsset "${e.targetAsset}" does not exist in item.assets or meta.assets.`,[...t,`targetAsset`],e.id),d=!1)}let f=e.type===`timeline`?e.events:e.type===`categorize`?e.items:void 0;if(f){let n=f.map(e=>e.id);new Set(n).size!==n.length&&(l.add(`error`,`DUPLICATE_ID`,`Internal IDs must be unique within the item.`,[...t,e.type===`timeline`?`events`:`items`],e.id),d=!1)}if(e.type===`chess-puzzle`&&e.correctAnswers.some(e=>e.length%2==0)&&l.add(`warning`,`CHESS_SEQUENCE_LENGTH`,`Each move sequence should end with the learner's move (an odd number of moves; the application plays the replies).`,[...t,`correctAnswers`],e.id),e.type===`note`)for(let n of[`content`,`hiddenContent`]){let r=e[n];r&&i.findHeadings(i.splitLines(r)).some(e=>e.level===1)&&l.add(`warning`,`NOTE_HEADING_LEVEL`,`Headings inside a note should start at level 2 (##); the note title is level 1.`,[...t,n],e.id)}let p=e.type===`fill-in-blanks`||e.type===`fill-in-select`;for(let[n,r]of s.itemRichText(e)){for(let i of x(r)){if(/^https:\/\//i.test(i))continue;let r=/^http:\/\//i.test(i);l.add(r?`warning`:u,`INVALID_IMAGE_URL`,r?`Image "${i}" uses http:// (prefer https://).`:`Image "${i}" needs an absolute https:// URL (use <asset:key /> for other media).`,[...t,...n],e.id)}!(p&&n.length===1&&n[0]===`text`)&&/<blank:[a-zA-Z0-9_-]+\s*\/>/i.test(r)&&l.add(`warning`,`BLANK_OUTSIDE_TEXT`,`Blank tags belong only in the text of fill-in-blanks and fill-in-select (shown here as an empty line).`,[...t,...n],e.id)}for(let[r,i]of s.itemRichText(e))for(let s of a.findAssetKeys(i))o.resolveAsset(s,e,n)||l.add(`warning`,`MISSING_ASSET`,`<asset:${s} /> does not exist in item.assets or meta.assets.`,[...t,...r],e.id);for(let r of Object.keys(e.assets??{}))n.assets?.[r]&&l.add(`warning`,`ASSET_SHADOWED`,`Item asset "${r}" shadows a global asset with the same key; use unique keys.`,[...t,`assets`,r],e.id);return d}function b(e,t,n,r,i){for(let[a,o]of Object.entries(e??{}))o.type!==`image`||o.altText?.trim()||(n.add(r===`strict`?`error`:`warning`,`MISSING_ALT_TEXT`,`Image "${a}" has no altText (alternative text for screen readers).`,[...t,a,`altText`],i),r===`load`&&(o.altText=S(o.value)||a))}function x(e){return[...e.replace(/^ {0,3}(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:^ {0,3}\1[`~]*[ \t]*$|(?![\s\S]))/gm,``).replace(/`[^`\n]+`/g,``).matchAll(/!\[[^\]\n]*\]\(\s*<?([^)\s>]+)/g)].map(e=>e[1])}function S(e){return(e.split(/[?#]/)[0].split(`/`).pop()??``).replace(/\.[a-z0-9]+$/i,``).replace(/[-_]+/g,` `).trim()}function C(e,t,n,r){let i=e.assets;if(!w(i))return!0;let a={};for(let[e,o]of Object.entries(i)){let i=e.toLowerCase();if(i in a)return n.add(`error`,`ASSET_KEY_COLLISION`,`Asset keys collide after lowercasing ("${e}").`,[...t,e],r),!1;i!==e&&n.add(`warning`,`ASSET_KEY_NORMALIZED`,`Asset key "${e}" was converted to "${i}".`,[...t,e],r),a[i]=o}e.assets=a;for(let t of[`thumbnail`,`targetAsset`])typeof e[t]==`string`&&(e[t]=e[t].toLowerCase());return!0}function w(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function T(e){return`Raw HTML ${e} is not allowed unless the set declares the "html" feature (use Markdown instead).`}function E(e,t){let n=e;for(let e of t.slice(0,-1))n=n[e];delete n[t[t.length-1]]}exports.OQSEValidationError=l,exports.OQSE_VERSION=f,exports.SUPPORTED_OQSE_MAJOR_VERSION=d,exports.formatOQSEIssues=u,exports.loadOQSEFile=p,exports.safeValidateOQSEFile=m,exports.validateOQSEFile=h;
|