taleem-player 1.0.10 → 1.0.12
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 +112 -14
- package/dist/css/taleem.css +5 -3
- package/dist/validation/index.js +172 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,25 +1,40 @@
|
|
|
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
|
+
|
|
27
|
+
---
|
|
28
|
+
## Visual Editor
|
|
29
|
+
|
|
30
|
+
Taleem Player has a companion visual editor.
|
|
31
|
+
|
|
32
|
+
Create structured `deck-v1` presentations in seconds — no manual slide layout required.
|
|
33
|
+
|
|
34
|
+
👉 https://bilza2023.github.io/taleem-editor/
|
|
35
|
+
|
|
36
|
+
The editor outputs valid Taleem JSON that runs directly in Taleem Player.
|
|
37
|
+
|
|
23
38
|
---
|
|
24
39
|
|
|
25
40
|
## What this project is
|
|
@@ -27,8 +42,9 @@ The live demo shows:
|
|
|
27
42
|
Taleem Player is a **presentation engine**, not a slide editor.
|
|
28
43
|
|
|
29
44
|
It takes **validated Taleem decks** and turns them into:
|
|
30
|
-
|
|
31
|
-
|
|
45
|
+
|
|
46
|
+
* index-based presentations
|
|
47
|
+
* time-based presentations
|
|
32
48
|
|
|
33
49
|
The system is **declarative**, **predictable**, and **CSS-driven**.
|
|
34
50
|
|
|
@@ -38,7 +54,7 @@ The system is **declarative**, **predictable**, and **CSS-driven**.
|
|
|
38
54
|
|
|
39
55
|
```bash
|
|
40
56
|
npm install taleem-player
|
|
41
|
-
|
|
57
|
+
```
|
|
42
58
|
|
|
43
59
|
---
|
|
44
60
|
|
|
@@ -137,6 +153,87 @@ They never change slide meaning.
|
|
|
137
153
|
|
|
138
154
|
---
|
|
139
155
|
|
|
156
|
+
## Validation & Authoring Utilities (offline)
|
|
157
|
+
|
|
158
|
+
Taleem Player also exposes **optional authoring utilities** for **offline use**
|
|
159
|
+
during deck creation, build steps, or publishing pipelines.
|
|
160
|
+
|
|
161
|
+
These utilities are **not runtime concerns** and are intentionally kept
|
|
162
|
+
separate from the player rendering path.
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
import {
|
|
166
|
+
validateDeckV1,
|
|
167
|
+
validatePlayerDeckV1,
|
|
168
|
+
normalizeDeckForPlayerV1,
|
|
169
|
+
} from "taleem-player/validation";
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### `validateDeckV1(deck)`
|
|
175
|
+
|
|
176
|
+
Schema-level validation.
|
|
177
|
+
|
|
178
|
+
* Ensures the deck conforms to **deck-v1**
|
|
179
|
+
* Validates slide types and data shapes
|
|
180
|
+
* Does **not** validate timing semantics
|
|
181
|
+
|
|
182
|
+
Returns `{ ok, value }` or `{ ok: false, errors }`.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
### `validatePlayerDeckV1(deck)`
|
|
187
|
+
|
|
188
|
+
Player-level semantic validation.
|
|
189
|
+
|
|
190
|
+
* Assumes schema validation has already passed
|
|
191
|
+
* Enforces:
|
|
192
|
+
|
|
193
|
+
* slide start / end correctness
|
|
194
|
+
* strict slide sequencing
|
|
195
|
+
* `showAt` within slide bounds
|
|
196
|
+
* EQ slide timing rules
|
|
197
|
+
* Does **not** mutate or fix data
|
|
198
|
+
|
|
199
|
+
This validator answers:
|
|
200
|
+
|
|
201
|
+
> “Can time safely move forward without ambiguity?”
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
### `normalizeDeckForPlayerV1(deck, options?)`
|
|
206
|
+
|
|
207
|
+
Offline normalization utility.
|
|
208
|
+
|
|
209
|
+
* Patches missing or invalid timing
|
|
210
|
+
* Assigns deterministic mock timings
|
|
211
|
+
* Ensures the deck is **safe for player playback**
|
|
212
|
+
* Intended for build steps, CLIs, or publishing workflows
|
|
213
|
+
|
|
214
|
+
This is **not validation** — it is a controlled fixer.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
### Usage model
|
|
219
|
+
|
|
220
|
+
**Static editors / CDN usage**
|
|
221
|
+
|
|
222
|
+
* no validation
|
|
223
|
+
* no fixing
|
|
224
|
+
* UX hints only
|
|
225
|
+
|
|
226
|
+
**Authoring / build / publish step**
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
normalize → validate → publish
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
These utilities are **explicit opt-in** and are never executed
|
|
233
|
+
by the player runtime.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
140
237
|
## CSS
|
|
141
238
|
|
|
142
239
|
```js
|
|
@@ -186,7 +283,8 @@ The core registry supports **additive registration only**.
|
|
|
186
283
|
|
|
187
284
|
The `eq` slide type is implemented and tested.
|
|
188
285
|
|
|
189
|
-
It represents **structured symbolic content** and intentionally marks the
|
|
286
|
+
It represents **structured symbolic content** and intentionally marks the
|
|
287
|
+
**upper boundary** of the system.
|
|
190
288
|
|
|
191
289
|
Anything more complex than this belongs in a **separate product**, not in the core player.
|
|
192
290
|
|
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/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
|
};
|