taleem-player 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -14
- package/dist/css/taleem.css +5 -3
- package/dist/spec/index.js +17 -6
- package/dist/validation/index.js +172 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
|
+
|
|
1
2
|
# Taleem Player
|
|
2
3
|
|
|
3
4
|
**Taleem Player** converts **Taleem JSON slide data** into **web-based presentations**.
|
|
4
5
|
|
|
5
6
|
It renders the **same Taleem JSON** in multiple display modes using a single, stable rendering engine.
|
|
6
7
|
|
|
7
|
-
> **Stable · deck-v1 frozen**
|
|
8
|
-
> The slide language, schema, and registry are complete.
|
|
8
|
+
> **Stable · deck-v1 frozen**
|
|
9
|
+
> The slide language, schema, and registry are complete.
|
|
9
10
|
> Internal improvements may continue without breaking public contracts.
|
|
10
11
|
|
|
11
12
|
---
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
## Demo & Documentation
|
|
15
|
+
|
|
16
|
+
👉 [https://bilza2023.github.io/taleem/](https://bilza2023.github.io/taleem/)
|
|
14
17
|
|
|
15
18
|
The live demo shows:
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
* Browser Mode (index-based rendering)
|
|
21
|
+
* Player Mode (time-based rendering)
|
|
22
|
+
* Real production Taleem JSON
|
|
23
|
+
* Shared CSS across all modes
|
|
24
|
+
* No screenshots. No mock data.
|
|
25
|
+
* What you see is the real engine running in the browser.
|
|
26
|
+
|
|
23
27
|
---
|
|
24
28
|
|
|
25
29
|
## What this project is
|
|
@@ -27,8 +31,9 @@ The live demo shows:
|
|
|
27
31
|
Taleem Player is a **presentation engine**, not a slide editor.
|
|
28
32
|
|
|
29
33
|
It takes **validated Taleem decks** and turns them into:
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
|
|
35
|
+
* index-based presentations
|
|
36
|
+
* time-based presentations
|
|
32
37
|
|
|
33
38
|
The system is **declarative**, **predictable**, and **CSS-driven**.
|
|
34
39
|
|
|
@@ -38,7 +43,7 @@ The system is **declarative**, **predictable**, and **CSS-driven**.
|
|
|
38
43
|
|
|
39
44
|
```bash
|
|
40
45
|
npm install taleem-player
|
|
41
|
-
|
|
46
|
+
```
|
|
42
47
|
|
|
43
48
|
---
|
|
44
49
|
|
|
@@ -137,6 +142,87 @@ They never change slide meaning.
|
|
|
137
142
|
|
|
138
143
|
---
|
|
139
144
|
|
|
145
|
+
## Validation & Authoring Utilities (offline)
|
|
146
|
+
|
|
147
|
+
Taleem Player also exposes **optional authoring utilities** for **offline use**
|
|
148
|
+
during deck creation, build steps, or publishing pipelines.
|
|
149
|
+
|
|
150
|
+
These utilities are **not runtime concerns** and are intentionally kept
|
|
151
|
+
separate from the player rendering path.
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
import {
|
|
155
|
+
validateDeckV1,
|
|
156
|
+
validatePlayerDeckV1,
|
|
157
|
+
normalizeDeckForPlayerV1,
|
|
158
|
+
} from "taleem-player/validation";
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### `validateDeckV1(deck)`
|
|
164
|
+
|
|
165
|
+
Schema-level validation.
|
|
166
|
+
|
|
167
|
+
* Ensures the deck conforms to **deck-v1**
|
|
168
|
+
* Validates slide types and data shapes
|
|
169
|
+
* Does **not** validate timing semantics
|
|
170
|
+
|
|
171
|
+
Returns `{ ok, value }` or `{ ok: false, errors }`.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
### `validatePlayerDeckV1(deck)`
|
|
176
|
+
|
|
177
|
+
Player-level semantic validation.
|
|
178
|
+
|
|
179
|
+
* Assumes schema validation has already passed
|
|
180
|
+
* Enforces:
|
|
181
|
+
|
|
182
|
+
* slide start / end correctness
|
|
183
|
+
* strict slide sequencing
|
|
184
|
+
* `showAt` within slide bounds
|
|
185
|
+
* EQ slide timing rules
|
|
186
|
+
* Does **not** mutate or fix data
|
|
187
|
+
|
|
188
|
+
This validator answers:
|
|
189
|
+
|
|
190
|
+
> “Can time safely move forward without ambiguity?”
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### `normalizeDeckForPlayerV1(deck, options?)`
|
|
195
|
+
|
|
196
|
+
Offline normalization utility.
|
|
197
|
+
|
|
198
|
+
* Patches missing or invalid timing
|
|
199
|
+
* Assigns deterministic mock timings
|
|
200
|
+
* Ensures the deck is **safe for player playback**
|
|
201
|
+
* Intended for build steps, CLIs, or publishing workflows
|
|
202
|
+
|
|
203
|
+
This is **not validation** — it is a controlled fixer.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### Usage model
|
|
208
|
+
|
|
209
|
+
**Static editors / CDN usage**
|
|
210
|
+
|
|
211
|
+
* no validation
|
|
212
|
+
* no fixing
|
|
213
|
+
* UX hints only
|
|
214
|
+
|
|
215
|
+
**Authoring / build / publish step**
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
normalize → validate → publish
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
These utilities are **explicit opt-in** and are never executed
|
|
222
|
+
by the player runtime.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
140
226
|
## CSS
|
|
141
227
|
|
|
142
228
|
```js
|
|
@@ -186,7 +272,8 @@ The core registry supports **additive registration only**.
|
|
|
186
272
|
|
|
187
273
|
The `eq` slide type is implemented and tested.
|
|
188
274
|
|
|
189
|
-
It represents **structured symbolic content** and intentionally marks the
|
|
275
|
+
It represents **structured symbolic content** and intentionally marks the
|
|
276
|
+
**upper boundary** of the system.
|
|
190
277
|
|
|
191
278
|
Anything more complex than this belongs in a **separate product**, not in the core player.
|
|
192
279
|
|
package/dist/css/taleem.css
CHANGED
|
@@ -405,9 +405,9 @@
|
|
|
405
405
|
/* ---------- Side panel items ---------- */
|
|
406
406
|
|
|
407
407
|
.slide.eq.imageRightBulletsLeft .eq-explain-item {
|
|
408
|
-
font-size: 1.
|
|
409
|
-
line-height: 1.
|
|
410
|
-
text-align:
|
|
408
|
+
font-size: 1.6rem;
|
|
409
|
+
line-height: 1.75;
|
|
410
|
+
text-align:justify;
|
|
411
411
|
|
|
412
412
|
word-wrap: break-word;
|
|
413
413
|
overflow-wrap: break-word;
|
|
@@ -415,6 +415,8 @@
|
|
|
415
415
|
background: rgba(255, 255, 255, 0.06);
|
|
416
416
|
padding: 14px 18px;
|
|
417
417
|
border-radius: 12px;
|
|
418
|
+
|
|
419
|
+
|
|
418
420
|
}
|
|
419
421
|
|
|
420
422
|
/* ---------- Side panel image ---------- */
|
package/dist/spec/index.js
CHANGED
|
@@ -50,7 +50,9 @@ var goldenDeckV1 = {
|
|
|
50
50
|
type: "imageSlide",
|
|
51
51
|
start: 15,
|
|
52
52
|
end: 20,
|
|
53
|
-
data: [
|
|
53
|
+
data: [
|
|
54
|
+
{ name: "image", content: "image.png", showAt: 15 }
|
|
55
|
+
]
|
|
54
56
|
},
|
|
55
57
|
{
|
|
56
58
|
type: "imageWithTitle",
|
|
@@ -121,7 +123,9 @@ var goldenDeckV1 = {
|
|
|
121
123
|
type: "progressbar",
|
|
122
124
|
start: 50,
|
|
123
125
|
end: 55,
|
|
124
|
-
data: [
|
|
126
|
+
data: [
|
|
127
|
+
{ name: "bar", label: "Lesson Coverage", value: 60, showAt: 50 }
|
|
128
|
+
]
|
|
125
129
|
},
|
|
126
130
|
{
|
|
127
131
|
type: "quoteSlide",
|
|
@@ -148,7 +152,12 @@ var goldenDeckV1 = {
|
|
|
148
152
|
start: 65,
|
|
149
153
|
end: 75,
|
|
150
154
|
data: [
|
|
151
|
-
{
|
|
155
|
+
{
|
|
156
|
+
name: "line",
|
|
157
|
+
type: "heading",
|
|
158
|
+
content: "Eq Slide - under construction",
|
|
159
|
+
showAt: 65
|
|
160
|
+
},
|
|
152
161
|
{
|
|
153
162
|
name: "line",
|
|
154
163
|
type: "math",
|
|
@@ -156,7 +165,7 @@ var goldenDeckV1 = {
|
|
|
156
165
|
showAt: 66,
|
|
157
166
|
spItems: [
|
|
158
167
|
{ type: "spText", content: "The square means the bracket is multiplied by itself." },
|
|
159
|
-
{ type: "spImage", content: "
|
|
168
|
+
{ type: "spImage", content: "image.png" }
|
|
160
169
|
]
|
|
161
170
|
},
|
|
162
171
|
{
|
|
@@ -166,7 +175,7 @@ var goldenDeckV1 = {
|
|
|
166
175
|
showAt: 67,
|
|
167
176
|
spItems: [
|
|
168
177
|
{ type: "spText", content: "The square means the bracket is multiplied by itself." },
|
|
169
|
-
{ type: "spImage", content: "image.
|
|
178
|
+
{ type: "spImage", content: "image.png" }
|
|
170
179
|
]
|
|
171
180
|
}
|
|
172
181
|
]
|
|
@@ -175,7 +184,9 @@ var goldenDeckV1 = {
|
|
|
175
184
|
type: "fillImage",
|
|
176
185
|
start: 75,
|
|
177
186
|
end: 80,
|
|
178
|
-
data: [
|
|
187
|
+
data: [
|
|
188
|
+
{ name: "image", content: "image.png", showAt: 75 }
|
|
189
|
+
]
|
|
179
190
|
},
|
|
180
191
|
{
|
|
181
192
|
type: "titleAndPara",
|
package/dist/validation/index.js
CHANGED
|
@@ -13960,7 +13960,179 @@ function validateDeckV1(deck) {
|
|
|
13960
13960
|
const r = zodDeckV1.safeParse(deck);
|
|
13961
13961
|
return r.success ? { ok: true, value: r.data } : { ok: false, errors: r.error.issues };
|
|
13962
13962
|
}
|
|
13963
|
+
|
|
13964
|
+
// src/validation/validatePlayerDeckV1.js
|
|
13965
|
+
function validatePlayerDeckV1(deck) {
|
|
13966
|
+
const errors = [];
|
|
13967
|
+
if (!deck || !Array.isArray(deck.deck)) {
|
|
13968
|
+
return fail("DECK_INVALID", "deck.deck must be an array");
|
|
13969
|
+
}
|
|
13970
|
+
const slides = deck.deck;
|
|
13971
|
+
validateSlideTiming(slides, errors);
|
|
13972
|
+
validateSlideSequence(slides, errors);
|
|
13973
|
+
validateShowAtRanges(slides, errors);
|
|
13974
|
+
validateEqSlides(slides, errors);
|
|
13975
|
+
return errors.length === 0 ? { ok: true } : { ok: false, errors };
|
|
13976
|
+
}
|
|
13977
|
+
function validateSlideTiming(slides, errors) {
|
|
13978
|
+
slides.forEach((slide, i) => {
|
|
13979
|
+
if (typeof slide.start !== "number" || typeof slide.end !== "number") {
|
|
13980
|
+
errors.push(err(
|
|
13981
|
+
"SLIDE_TIME_MISSING",
|
|
13982
|
+
`Slide ${i} must have numeric start/end`
|
|
13983
|
+
));
|
|
13984
|
+
return;
|
|
13985
|
+
}
|
|
13986
|
+
if (slide.start >= slide.end) {
|
|
13987
|
+
errors.push(err(
|
|
13988
|
+
"SLIDE_TIME_ORDER",
|
|
13989
|
+
`Slide ${i} start (${slide.start}) must be < end (${slide.end})`
|
|
13990
|
+
));
|
|
13991
|
+
}
|
|
13992
|
+
if (i === 0 && slide.start !== 0) {
|
|
13993
|
+
errors.push(err(
|
|
13994
|
+
"DECK_START_NOT_ZERO",
|
|
13995
|
+
`First slide must start at 0 (got ${slide.start})`
|
|
13996
|
+
));
|
|
13997
|
+
}
|
|
13998
|
+
});
|
|
13999
|
+
}
|
|
14000
|
+
function validateSlideSequence(slides, errors) {
|
|
14001
|
+
for (let i = 0; i < slides.length - 1; i++) {
|
|
14002
|
+
const a = slides[i];
|
|
14003
|
+
const b = slides[i + 1];
|
|
14004
|
+
if (a.end !== b.start) {
|
|
14005
|
+
errors.push(err(
|
|
14006
|
+
"SLIDE_SEQUENCE_BREAK",
|
|
14007
|
+
`Slide ${i} ends at ${a.end} but next starts at ${b.start}`
|
|
14008
|
+
));
|
|
14009
|
+
}
|
|
14010
|
+
if (b.start < a.start) {
|
|
14011
|
+
errors.push(err(
|
|
14012
|
+
"SLIDE_TIME_REVERSE",
|
|
14013
|
+
`Slide ${i + 1} starts before previous slide`
|
|
14014
|
+
));
|
|
14015
|
+
}
|
|
14016
|
+
}
|
|
14017
|
+
}
|
|
14018
|
+
function validateShowAtRanges(slides, errors) {
|
|
14019
|
+
slides.forEach((slide, si) => {
|
|
14020
|
+
let lastShowAt = null;
|
|
14021
|
+
if (!Array.isArray(slide.data)) return;
|
|
14022
|
+
slide.data.forEach((item, di) => {
|
|
14023
|
+
if (typeof item.showAt !== "number") return;
|
|
14024
|
+
if (item.showAt < slide.start || item.showAt >= slide.end) {
|
|
14025
|
+
errors.push(err(
|
|
14026
|
+
"SHOWAT_OUT_OF_RANGE",
|
|
14027
|
+
`Slide ${si} item ${di} showAt ${item.showAt} outside [${slide.start}, ${slide.end})`
|
|
14028
|
+
));
|
|
14029
|
+
}
|
|
14030
|
+
if (lastShowAt !== null && item.showAt < lastShowAt) {
|
|
14031
|
+
errors.push(err(
|
|
14032
|
+
"SHOWAT_ORDER",
|
|
14033
|
+
`Slide ${si} item ${di} showAt decreases (${item.showAt} < ${lastShowAt})`
|
|
14034
|
+
));
|
|
14035
|
+
}
|
|
14036
|
+
lastShowAt = item.showAt;
|
|
14037
|
+
});
|
|
14038
|
+
});
|
|
14039
|
+
}
|
|
14040
|
+
function validateEqSlides(slides, errors) {
|
|
14041
|
+
slides.forEach((slide, si) => {
|
|
14042
|
+
if (slide.type !== "eq") return;
|
|
14043
|
+
let lastLineShowAt = null;
|
|
14044
|
+
slide.data.forEach((line, li) => {
|
|
14045
|
+
if (typeof line.showAt === "number") {
|
|
14046
|
+
if (line.showAt < slide.start || line.showAt >= slide.end) {
|
|
14047
|
+
errors.push(err(
|
|
14048
|
+
"EQ_LINE_SHOWAT_RANGE",
|
|
14049
|
+
`EQ slide ${si} line ${li} showAt ${line.showAt} outside slide range`
|
|
14050
|
+
));
|
|
14051
|
+
}
|
|
14052
|
+
if (lastLineShowAt !== null && line.showAt < lastLineShowAt) {
|
|
14053
|
+
errors.push(err(
|
|
14054
|
+
"EQ_LINE_ORDER",
|
|
14055
|
+
`EQ slide ${si} line ${li} showAt decreases`
|
|
14056
|
+
));
|
|
14057
|
+
}
|
|
14058
|
+
lastLineShowAt = line.showAt;
|
|
14059
|
+
}
|
|
14060
|
+
if (Array.isArray(line.spItems)) {
|
|
14061
|
+
line.spItems.forEach((sp, spi) => {
|
|
14062
|
+
if ("showAt" in sp) {
|
|
14063
|
+
errors.push(err(
|
|
14064
|
+
"EQ_SPITEM_SHOWAT_FORBIDDEN",
|
|
14065
|
+
`EQ slide ${si} line ${li} spItem ${spi} must not define showAt`
|
|
14066
|
+
));
|
|
14067
|
+
}
|
|
14068
|
+
});
|
|
14069
|
+
}
|
|
14070
|
+
});
|
|
14071
|
+
});
|
|
14072
|
+
}
|
|
14073
|
+
function err(code, message) {
|
|
14074
|
+
return { code, message };
|
|
14075
|
+
}
|
|
14076
|
+
function fail(code, message) {
|
|
14077
|
+
return { ok: false, errors: [{ code, message }] };
|
|
14078
|
+
}
|
|
14079
|
+
|
|
14080
|
+
// src/validation/normalizeDeckForPlayerV1.js
|
|
14081
|
+
function normalizeDeckForPlayerV1(deck, opts = {}) {
|
|
14082
|
+
const {
|
|
14083
|
+
slideDuration = 5,
|
|
14084
|
+
// default seconds per slide
|
|
14085
|
+
itemStep = 1
|
|
14086
|
+
// default gap between showAt items
|
|
14087
|
+
} = opts;
|
|
14088
|
+
let time3 = 0;
|
|
14089
|
+
const slides = deck.deck.map((slide) => {
|
|
14090
|
+
const start = isNumber(slide.start) ? slide.start : time3;
|
|
14091
|
+
const end = isNumber(slide.end) && slide.end > start ? slide.end : start + slideDuration;
|
|
14092
|
+
const fixedSlide = {
|
|
14093
|
+
...slide,
|
|
14094
|
+
start,
|
|
14095
|
+
end,
|
|
14096
|
+
data: fixShowAt(slide.data, start, end, itemStep)
|
|
14097
|
+
};
|
|
14098
|
+
time3 = end;
|
|
14099
|
+
return fixedSlide;
|
|
14100
|
+
});
|
|
14101
|
+
return {
|
|
14102
|
+
...deck,
|
|
14103
|
+
deck: slides
|
|
14104
|
+
};
|
|
14105
|
+
}
|
|
14106
|
+
function fixShowAt(data, start, end, step) {
|
|
14107
|
+
if (!Array.isArray(data)) return data;
|
|
14108
|
+
let t = start;
|
|
14109
|
+
return data.map((item) => {
|
|
14110
|
+
const showAt2 = isNumber(item.showAt) ? clamp(item.showAt, start, end - 1e-4) : t;
|
|
14111
|
+
t = showAt2 + step;
|
|
14112
|
+
if (item.name === "line" && Array.isArray(item.spItems)) {
|
|
14113
|
+
return {
|
|
14114
|
+
...item,
|
|
14115
|
+
showAt: showAt2,
|
|
14116
|
+
spItems: item.spItems.map((sp) => stripShowAt(sp))
|
|
14117
|
+
};
|
|
14118
|
+
}
|
|
14119
|
+
return { ...item, showAt: showAt2 };
|
|
14120
|
+
});
|
|
14121
|
+
}
|
|
14122
|
+
function stripShowAt(obj) {
|
|
14123
|
+
if (!obj || typeof obj !== "object") return obj;
|
|
14124
|
+
const { showAt: showAt2, ...rest } = obj;
|
|
14125
|
+
return rest;
|
|
14126
|
+
}
|
|
14127
|
+
function clamp(v, min, max) {
|
|
14128
|
+
return Math.min(Math.max(v, min), max);
|
|
14129
|
+
}
|
|
14130
|
+
function isNumber(v) {
|
|
14131
|
+
return typeof v === "number" && !Number.isNaN(v);
|
|
14132
|
+
}
|
|
13963
14133
|
export {
|
|
14134
|
+
normalizeDeckForPlayerV1,
|
|
13964
14135
|
validateDeckV1,
|
|
14136
|
+
validatePlayerDeckV1,
|
|
13965
14137
|
zodDeckV1
|
|
13966
14138
|
};
|