@thanh01.pmt/presentation-kit 0.1.1 → 0.2.1
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/{chunk-RCHOTRWI.js → chunk-PAHIW5IX.js} +906 -16
- package/dist/chunk-PAHIW5IX.js.map +1 -0
- package/dist/{chunk-N4Q6GUAQ.cjs → chunk-RJMPTRL3.cjs} +920 -15
- package/dist/chunk-RJMPTRL3.cjs.map +1 -0
- package/dist/{click-css-CadkoJ_A.d.cts → click-css-C84mdmU0.d.cts} +37 -8
- package/dist/{click-css-CadkoJ_A.d.ts → click-css-C84mdmU0.d.ts} +37 -8
- package/dist/index.cjs +244 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -3
- package/dist/index.d.ts +87 -3
- package/dist/index.js +172 -3
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +726 -21
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +50 -6
- package/dist/react/index.d.ts +50 -6
- package/dist/react/index.js +716 -13
- package/dist/react/index.js.map +1 -1
- package/package.json +11 -3
- package/dist/chunk-N4Q6GUAQ.cjs.map +0 -1
- package/dist/chunk-RCHOTRWI.js.map +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import yaml from 'js-yaml';
|
|
1
2
|
import { Marp } from '@marp-team/marp-core';
|
|
2
3
|
|
|
3
4
|
// src/core/arrow-converter.ts
|
|
@@ -388,9 +389,13 @@ function generateScopedCss(tokens, containerId = "marp-container") {
|
|
|
388
389
|
}
|
|
389
390
|
|
|
390
391
|
/* Slide Inner Section (1280x720 Canvas Coordinate Scale) */
|
|
392
|
+
#${safeId} section:not([data-background-color]):not([style*="background"]),
|
|
393
|
+
#${safeId} foreignObject > section:not([data-background-color]):not([style*="background"]) {
|
|
394
|
+
background-color: ${tokens.background} !important;
|
|
395
|
+
}
|
|
396
|
+
|
|
391
397
|
#${safeId} section,
|
|
392
398
|
#${safeId} foreignObject > section {
|
|
393
|
-
background-color: ${tokens.background} !important;
|
|
394
399
|
color: ${tokens.text} !important;
|
|
395
400
|
font-family: var(--font-sans), Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
|
|
396
401
|
padding: 50px 60px !important;
|
|
@@ -636,6 +641,43 @@ div#${safeId} svg foreignObject section table tr:hover td,
|
|
|
636
641
|
padding-top: 6px !important;
|
|
637
642
|
}
|
|
638
643
|
|
|
644
|
+
/* Multi-column Grid & Flex Layout Utilities */
|
|
645
|
+
#${safeId} section .columns,
|
|
646
|
+
#${safeId} section .grid-2 {
|
|
647
|
+
display: grid !important;
|
|
648
|
+
grid-template-columns: 1fr 1fr !important;
|
|
649
|
+
gap: 28px !important;
|
|
650
|
+
width: 100% !important;
|
|
651
|
+
margin: 16px 0 !important;
|
|
652
|
+
align-items: start !important;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
#${safeId} section .columns > div,
|
|
656
|
+
#${safeId} section .grid-2 > div {
|
|
657
|
+
min-width: 0 !important;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
#${safeId} section .card {
|
|
661
|
+
background: ${tokens.quoteBg} !important;
|
|
662
|
+
border: 1px solid ${tokens.cardBorder} !important;
|
|
663
|
+
border-radius: 10px !important;
|
|
664
|
+
padding: 18px 22px !important;
|
|
665
|
+
margin: 12px 0 !important;
|
|
666
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
#${safeId} section .card.highlight {
|
|
670
|
+
border-left: 4px solid ${tokens.link} !important;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
#${safeId} section .card.warning {
|
|
674
|
+
border-left: 4px solid #f59e0b !important;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
#${safeId} section .card.success {
|
|
678
|
+
border-left: 4px solid #10b981 !important;
|
|
679
|
+
}
|
|
680
|
+
|
|
639
681
|
/* Marp Split Layout Container */
|
|
640
682
|
#${safeId} section[data-marpit-advanced-background="background"] {
|
|
641
683
|
display: flex !important;
|
|
@@ -1161,6 +1203,847 @@ ${prefix}[data-click-group].visible {
|
|
|
1161
1203
|
|
|
1162
1204
|
`;
|
|
1163
1205
|
}
|
|
1206
|
+
function isLayoutComment(comment) {
|
|
1207
|
+
const trimmed = comment.trim();
|
|
1208
|
+
return trimmed.startsWith("layout:") || trimmed.startsWith("layout :");
|
|
1209
|
+
}
|
|
1210
|
+
function parseLayoutFromComment(comment) {
|
|
1211
|
+
try {
|
|
1212
|
+
const trimmed = comment.trim();
|
|
1213
|
+
if (!isLayoutComment(trimmed)) return null;
|
|
1214
|
+
const parsed = yaml.load(trimmed);
|
|
1215
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
1216
|
+
const boxes = parsed.boxes ?? parsed.layout?.boxes;
|
|
1217
|
+
if (Array.isArray(boxes)) {
|
|
1218
|
+
return { boxes };
|
|
1219
|
+
}
|
|
1220
|
+
return null;
|
|
1221
|
+
} catch (err) {
|
|
1222
|
+
console.warn("[presentation-kit] Failed to parse layout comment YAML:", err);
|
|
1223
|
+
return null;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
function filterPresenterNotes(comments) {
|
|
1227
|
+
return comments.map((c) => c.trim()).filter((c) => {
|
|
1228
|
+
if (!c) return false;
|
|
1229
|
+
if (isLayoutComment(c)) return false;
|
|
1230
|
+
if (/^_?[a-zA-Z0-9_-]+\s*:/i.test(c) && !c.toLowerCase().startsWith("note:")) {
|
|
1231
|
+
return false;
|
|
1232
|
+
}
|
|
1233
|
+
return true;
|
|
1234
|
+
}).map((c) => {
|
|
1235
|
+
return c.replace(/^_?note:\s*/i, "").trim();
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
function extractLayoutsAndNotes(rawComments) {
|
|
1239
|
+
const layouts = [];
|
|
1240
|
+
const notes = [];
|
|
1241
|
+
for (const slideComments of rawComments) {
|
|
1242
|
+
if (!slideComments || slideComments.length === 0) {
|
|
1243
|
+
layouts.push(null);
|
|
1244
|
+
notes.push([]);
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
let slideLayout = null;
|
|
1248
|
+
for (const comment of slideComments) {
|
|
1249
|
+
if (isLayoutComment(comment)) {
|
|
1250
|
+
const parsed = parseLayoutFromComment(comment);
|
|
1251
|
+
if (parsed) {
|
|
1252
|
+
slideLayout = parsed;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
layouts.push(slideLayout);
|
|
1257
|
+
notes.push(filterPresenterNotes(slideComments));
|
|
1258
|
+
}
|
|
1259
|
+
return { layouts, notes };
|
|
1260
|
+
}
|
|
1261
|
+
function serializeLayoutToComment(layout) {
|
|
1262
|
+
if (!layout.boxes || layout.boxes.length === 0) return "";
|
|
1263
|
+
const cleanData = {
|
|
1264
|
+
boxes: layout.boxes.map((box) => {
|
|
1265
|
+
const b = {
|
|
1266
|
+
id: box.id,
|
|
1267
|
+
target: box.target || `h1`,
|
|
1268
|
+
pos: [
|
|
1269
|
+
Math.round(box.pos[0]),
|
|
1270
|
+
Math.round(box.pos[1]),
|
|
1271
|
+
Math.round(box.pos[2]),
|
|
1272
|
+
Math.round(box.pos[3])
|
|
1273
|
+
]
|
|
1274
|
+
};
|
|
1275
|
+
if (box.fontSize) b.fontSize = box.fontSize;
|
|
1276
|
+
if (box.color) b.color = box.color;
|
|
1277
|
+
if (box.textAlign) b.textAlign = box.textAlign;
|
|
1278
|
+
if (box.style) b.style = box.style;
|
|
1279
|
+
return b;
|
|
1280
|
+
})
|
|
1281
|
+
};
|
|
1282
|
+
const yamlStr = yaml.dump(cleanData, {
|
|
1283
|
+
indent: 2,
|
|
1284
|
+
lineWidth: -1,
|
|
1285
|
+
noRefs: true
|
|
1286
|
+
}).trim();
|
|
1287
|
+
return `<!-- layout:
|
|
1288
|
+
${yamlStr.split("\n").join("\n ")}
|
|
1289
|
+
-->`;
|
|
1290
|
+
}
|
|
1291
|
+
function splitMarkdownSlides(markdown) {
|
|
1292
|
+
const lines = markdown.split("\n");
|
|
1293
|
+
let frontmatter = null;
|
|
1294
|
+
const slideBuffers = [];
|
|
1295
|
+
let currentBuffer = [];
|
|
1296
|
+
let inCodeFence = false;
|
|
1297
|
+
let lineIdx = 0;
|
|
1298
|
+
if (lines.length > 0 && lines[0].trim() === "---") {
|
|
1299
|
+
const fmBuffer = [lines[0]];
|
|
1300
|
+
lineIdx = 1;
|
|
1301
|
+
while (lineIdx < lines.length) {
|
|
1302
|
+
const line = lines[lineIdx];
|
|
1303
|
+
fmBuffer.push(line);
|
|
1304
|
+
if (line.trim() === "---") {
|
|
1305
|
+
frontmatter = fmBuffer.join("\n");
|
|
1306
|
+
lineIdx++;
|
|
1307
|
+
break;
|
|
1308
|
+
}
|
|
1309
|
+
lineIdx++;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
for (let i = lineIdx; i < lines.length; i++) {
|
|
1313
|
+
const line = lines[i];
|
|
1314
|
+
if (/^```/.test(line.trim())) {
|
|
1315
|
+
inCodeFence = !inCodeFence;
|
|
1316
|
+
currentBuffer.push(line);
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
if (!inCodeFence && line.trim() === "---") {
|
|
1320
|
+
slideBuffers.push(currentBuffer);
|
|
1321
|
+
currentBuffer = [];
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
currentBuffer.push(line);
|
|
1325
|
+
}
|
|
1326
|
+
slideBuffers.push(currentBuffer);
|
|
1327
|
+
const slides = slideBuffers.map((b) => b.join("\n"));
|
|
1328
|
+
return { frontmatter, slides };
|
|
1329
|
+
}
|
|
1330
|
+
function updateSlideLayoutInMarkdown(markdown, slideIndex, layout) {
|
|
1331
|
+
const { frontmatter, slides } = splitMarkdownSlides(markdown);
|
|
1332
|
+
if (slideIndex < 0 || slideIndex >= slides.length) {
|
|
1333
|
+
return markdown;
|
|
1334
|
+
}
|
|
1335
|
+
let slideContent = slides[slideIndex];
|
|
1336
|
+
const layoutCommentRegex = /<!--\s*layout:\s*[\s\S]*?-->/i;
|
|
1337
|
+
const newComment = layout && layout.boxes && layout.boxes.length > 0 ? serializeLayoutToComment(layout) : "";
|
|
1338
|
+
if (layoutCommentRegex.test(slideContent)) {
|
|
1339
|
+
if (newComment) {
|
|
1340
|
+
slideContent = slideContent.replace(layoutCommentRegex, newComment);
|
|
1341
|
+
} else {
|
|
1342
|
+
slideContent = slideContent.replace(layoutCommentRegex, "").trim();
|
|
1343
|
+
}
|
|
1344
|
+
} else if (newComment) {
|
|
1345
|
+
const trimmed = slideContent.trimStart();
|
|
1346
|
+
slideContent = `${newComment}
|
|
1347
|
+
|
|
1348
|
+
${trimmed}`;
|
|
1349
|
+
}
|
|
1350
|
+
slides[slideIndex] = slideContent;
|
|
1351
|
+
const joinedSlides = slides.join("\n---\n");
|
|
1352
|
+
if (frontmatter) {
|
|
1353
|
+
return `${frontmatter}
|
|
1354
|
+
|
|
1355
|
+
${joinedSlides}`;
|
|
1356
|
+
}
|
|
1357
|
+
return joinedSlides;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// src/core/layout/css-generator.ts
|
|
1361
|
+
function generateLayoutCss(layouts, containerId = "marp-container") {
|
|
1362
|
+
const cssRules = [];
|
|
1363
|
+
layouts.forEach((layout, index) => {
|
|
1364
|
+
if (!layout || !layout.boxes || layout.boxes.length === 0) return;
|
|
1365
|
+
const slideNum = index + 1;
|
|
1366
|
+
const slideSectionSelector = `#${containerId} svg[data-marpit-svg]:nth-of-type(${slideNum}) section, #${containerId} .marpit > svg:nth-of-type(${slideNum}) section`;
|
|
1367
|
+
cssRules.push(`
|
|
1368
|
+
/* Slide ${slideNum} Layout Container */
|
|
1369
|
+
${slideSectionSelector} {
|
|
1370
|
+
position: relative !important;
|
|
1371
|
+
display: block !important;
|
|
1372
|
+
}`);
|
|
1373
|
+
for (const box of layout.boxes) {
|
|
1374
|
+
if (!box || !Array.isArray(box.pos) || box.pos.length < 4) continue;
|
|
1375
|
+
const [x, y, w, h] = box.pos;
|
|
1376
|
+
const targetSelector = box.target || `[data-box-id="${box.id}"]`;
|
|
1377
|
+
const ruleSelector = `${slideSectionSelector} ${targetSelector}`;
|
|
1378
|
+
const styles = [
|
|
1379
|
+
`position: absolute !important`,
|
|
1380
|
+
`left: ${Math.round(x)}px !important`,
|
|
1381
|
+
`top: ${Math.round(y)}px !important`,
|
|
1382
|
+
`width: ${Math.round(w)}px !important`,
|
|
1383
|
+
`min-height: ${Math.round(h)}px !important`,
|
|
1384
|
+
`box-sizing: border-box !important`,
|
|
1385
|
+
`margin: 0 !important`
|
|
1386
|
+
];
|
|
1387
|
+
if (box.fontSize) {
|
|
1388
|
+
styles.push(`font-size: ${box.fontSize} !important`);
|
|
1389
|
+
}
|
|
1390
|
+
if (box.color) {
|
|
1391
|
+
styles.push(`color: ${box.color} !important`);
|
|
1392
|
+
}
|
|
1393
|
+
if (box.textAlign) {
|
|
1394
|
+
styles.push(`text-align: ${box.textAlign} !important`);
|
|
1395
|
+
}
|
|
1396
|
+
if (box.style) {
|
|
1397
|
+
styles.push(box.style);
|
|
1398
|
+
}
|
|
1399
|
+
cssRules.push(`
|
|
1400
|
+
${ruleSelector} {
|
|
1401
|
+
${styles.join(";\n ")};
|
|
1402
|
+
}`);
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
return cssRules.join("\n");
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// src/core/layout/presets.ts
|
|
1409
|
+
var SLIDE_LAYOUT_PRESETS = [
|
|
1410
|
+
{
|
|
1411
|
+
id: "title-hero",
|
|
1412
|
+
name: "B\xECa & Gi\u1EDBi thi\u1EC7u (Title Cover)",
|
|
1413
|
+
category: "cover",
|
|
1414
|
+
description: "Slide b\xECa m\u1EDF \u0111\u1EA7u \u1EA5n t\u01B0\u1EE3ng v\u1EDBi ti\xEAu \u0111\u1EC1 l\u1EDBn, ph\u1EE5 \u0111\u1EC1 v\xE0 th\xF4ng tin gi\u1EA3ng vi\xEAn.",
|
|
1415
|
+
layout: {
|
|
1416
|
+
boxes: [
|
|
1417
|
+
{
|
|
1418
|
+
id: "title",
|
|
1419
|
+
target: "h1",
|
|
1420
|
+
pos: [100, 180, 1080, 140],
|
|
1421
|
+
fontSize: "3rem",
|
|
1422
|
+
textAlign: "center"
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
id: "subtitle",
|
|
1426
|
+
target: "h3",
|
|
1427
|
+
pos: [100, 350, 1080, 70],
|
|
1428
|
+
fontSize: "1.5rem",
|
|
1429
|
+
textAlign: "center"
|
|
1430
|
+
},
|
|
1431
|
+
{
|
|
1432
|
+
id: "speaker-meta",
|
|
1433
|
+
target: "p",
|
|
1434
|
+
pos: [100, 460, 1080, 80],
|
|
1435
|
+
fontSize: "1.1rem",
|
|
1436
|
+
textAlign: "center"
|
|
1437
|
+
}
|
|
1438
|
+
]
|
|
1439
|
+
},
|
|
1440
|
+
markdownSnippet: `<!-- layout:
|
|
1441
|
+
boxes:
|
|
1442
|
+
- id: title
|
|
1443
|
+
target: h1
|
|
1444
|
+
pos: [100, 180, 1080, 140]
|
|
1445
|
+
fontSize: 3rem
|
|
1446
|
+
textAlign: center
|
|
1447
|
+
- id: subtitle
|
|
1448
|
+
target: h3
|
|
1449
|
+
pos: [100, 350, 1080, 70]
|
|
1450
|
+
fontSize: 1.5rem
|
|
1451
|
+
textAlign: center
|
|
1452
|
+
- id: speaker-meta
|
|
1453
|
+
target: p
|
|
1454
|
+
pos: [100, 460, 1080, 80]
|
|
1455
|
+
fontSize: 1.1rem
|
|
1456
|
+
textAlign: center
|
|
1457
|
+
-->
|
|
1458
|
+
|
|
1459
|
+
# \u{1F680} Ti\xEAu \u0110\u1EC1 B\xE0i Gi\u1EA3ng Ch\xEDnh
|
|
1460
|
+
### Ph\u1EE5 \u0111\u1EC1 ng\u1EAFn g\u1ECDn, truy\u1EC1n c\u1EA3m h\u1EE9ng v\xE0 \u0111\u1ECBnh v\u1ECB m\u1EE5c ti\xEAu
|
|
1461
|
+
|
|
1462
|
+
**Gi\u1EA3ng vi\xEAn:** Th\u1EA7y Nguy\u1EC5n V\u0103n A | **Th\u1EDDi l\u01B0\u1EE3ng:** 45 ph\xFAt | **C\u1EA5p \u0111\u1ED9:** Trung c\u1EA5p
|
|
1463
|
+
|
|
1464
|
+
<!--
|
|
1465
|
+
Presenter Notes:
|
|
1466
|
+
- Ch\xE0o m\u1EEBng h\u1ECDc vi\xEAn, gi\u1EDBi thi\u1EC7u m\u1EE5c ti\xEAu b\xE0i h\u1ECDc h\xF4m nay.
|
|
1467
|
+
- Kh\u01A1i g\u1EE3i s\u1EF1 t\xF2 m\xF2 b\u1EB1ng c\xE2u h\u1ECFi m\u1EDF \u0111\u1EA7u v\u1EC1 b\xE0i to\xE1n th\u1EF1c t\u1EBF.
|
|
1468
|
+
-->`
|
|
1469
|
+
},
|
|
1470
|
+
{
|
|
1471
|
+
id: "two-column-split",
|
|
1472
|
+
name: "So S\xE1nh 2 C\u1ED9t (2-Column Comparison)",
|
|
1473
|
+
category: "split",
|
|
1474
|
+
description: "B\u1ED1 c\u1EE5c chia \u0111\xF4i m\xE0n h\xECnh \u0111\u1EC3 so s\xE1nh ph\u01B0\u01A1ng ph\xE1p c\u0169 vs m\u1EDBi, l\xFD thuy\u1EBFt vs th\u1EF1c t\u1EBF.",
|
|
1475
|
+
layout: {
|
|
1476
|
+
boxes: [
|
|
1477
|
+
{
|
|
1478
|
+
id: "heading",
|
|
1479
|
+
target: "h2",
|
|
1480
|
+
pos: [80, 50, 1120, 70],
|
|
1481
|
+
fontSize: "2rem"
|
|
1482
|
+
},
|
|
1483
|
+
{
|
|
1484
|
+
id: "col-left",
|
|
1485
|
+
target: ".columns-2 > div:first-child",
|
|
1486
|
+
pos: [80, 150, 530, 510]
|
|
1487
|
+
},
|
|
1488
|
+
{
|
|
1489
|
+
id: "col-right",
|
|
1490
|
+
target: ".columns-2 > div:last-child",
|
|
1491
|
+
pos: [670, 150, 530, 510]
|
|
1492
|
+
}
|
|
1493
|
+
]
|
|
1494
|
+
},
|
|
1495
|
+
markdownSnippet: `<!-- layout:
|
|
1496
|
+
boxes:
|
|
1497
|
+
- id: heading
|
|
1498
|
+
target: h2
|
|
1499
|
+
pos: [80, 50, 1120, 70]
|
|
1500
|
+
fontSize: 2rem
|
|
1501
|
+
- id: col-left
|
|
1502
|
+
target: '.columns-2 > div:first-child'
|
|
1503
|
+
pos: [80, 150, 530, 510]
|
|
1504
|
+
- id: col-right
|
|
1505
|
+
target: '.columns-2 > div:last-child'
|
|
1506
|
+
pos: [670, 150, 530, 510]
|
|
1507
|
+
-->
|
|
1508
|
+
|
|
1509
|
+
## \u2696\uFE0F So S\xE1nh Kh\xE1i Ni\u1EC7m & Gi\u1EA3i Ph\xE1p
|
|
1510
|
+
|
|
1511
|
+
<div class="columns-2">
|
|
1512
|
+
<div>
|
|
1513
|
+
|
|
1514
|
+
### \u{1F534} C\xE1ch Ti\u1EBFp C\u1EADn C\u0169
|
|
1515
|
+
- X\u1EED l\xFD th\u1EE7 c\xF4ng, t\u1ED1n nhi\u1EC1u nh\xE2n l\u1EF1c
|
|
1516
|
+
- D\u1EC5 ph\xE1t sinh sai s\u1ED1 do thao t\xE1c l\u1EB7p l\u1EA1i
|
|
1517
|
+
- Kh\xF3 m\u1EDF r\u1ED9ng khi t\u1EA3i t\u0103ng \u0111\u1ED9t bi\u1EBFn
|
|
1518
|
+
|
|
1519
|
+
</div>
|
|
1520
|
+
<div>
|
|
1521
|
+
|
|
1522
|
+
### \u{1F7E2} Gi\u1EA3i Ph\xE1p T\u1ED1i \u01AFu M\u1EDBi
|
|
1523
|
+
- T\u1EF1 \u0111\u1ED9ng h\xF3a ho\xE0n to\xE0n lu\u1ED3ng x\u1EED l\xFD
|
|
1524
|
+
- Gi\xE1m s\xE1t tr\u1EA1ng th\xE1i th\u1EDDi gian th\u1EF1c
|
|
1525
|
+
- Kh\u1EA3 n\u0103ng co gi\xE3n linh ho\u1EA1t theo nhu c\u1EA7u
|
|
1526
|
+
|
|
1527
|
+
</div>
|
|
1528
|
+
</div>
|
|
1529
|
+
|
|
1530
|
+
<!--
|
|
1531
|
+
Presenter Notes:
|
|
1532
|
+
- Nh\u1EA5n m\u1EA1nh \u0111i\u1EC3m ngh\u1EBDn l\u1EDBn nh\u1EA5t \u1EDF c\u1ED9t b\xEAn tr\xE1i.
|
|
1533
|
+
- D\u1EABn ch\u1EE9ng s\u1ED1 li\u1EC7u th\u1EF1c t\u1EBF ch\u1EE9ng minh t\xEDnh hi\u1EC7u qu\u1EA3 c\u1EE7a c\u1ED9t ph\u1EA3i.
|
|
1534
|
+
-->`
|
|
1535
|
+
},
|
|
1536
|
+
{
|
|
1537
|
+
id: "code-explainer",
|
|
1538
|
+
name: "Gi\u1EA3i Th\xEDch Code (Code Walkthrough)",
|
|
1539
|
+
category: "code",
|
|
1540
|
+
description: "C\u1ED9t tr\xE1i hi\u1EC3n th\u1ECB code snippet c\xF3 \u0111\xE1nh s\u1ED1, c\u1ED9t ph\u1EA3i gi\u1EA3i th\xEDch t\u1EEBng b\u01B0\u1EDBc ho\u1EA1t \u0111\u1ED9ng.",
|
|
1541
|
+
layout: {
|
|
1542
|
+
boxes: [
|
|
1543
|
+
{
|
|
1544
|
+
id: "heading",
|
|
1545
|
+
target: "h2",
|
|
1546
|
+
pos: [80, 45, 1120, 65],
|
|
1547
|
+
fontSize: "1.9rem"
|
|
1548
|
+
},
|
|
1549
|
+
{
|
|
1550
|
+
id: "code-box",
|
|
1551
|
+
target: ".columns-2 > div:first-child",
|
|
1552
|
+
pos: [80, 135, 630, 535]
|
|
1553
|
+
},
|
|
1554
|
+
{
|
|
1555
|
+
id: "notes-box",
|
|
1556
|
+
target: ".columns-2 > div:last-child",
|
|
1557
|
+
pos: [740, 135, 460, 535]
|
|
1558
|
+
}
|
|
1559
|
+
]
|
|
1560
|
+
},
|
|
1561
|
+
markdownSnippet: `<!-- layout:
|
|
1562
|
+
boxes:
|
|
1563
|
+
- id: heading
|
|
1564
|
+
target: h2
|
|
1565
|
+
pos: [80, 45, 1120, 65]
|
|
1566
|
+
fontSize: 1.9rem
|
|
1567
|
+
- id: code-box
|
|
1568
|
+
target: '.columns-2 > div:first-child'
|
|
1569
|
+
pos: [80, 135, 630, 535]
|
|
1570
|
+
- id: notes-box
|
|
1571
|
+
target: '.columns-2 > div:last-child'
|
|
1572
|
+
pos: [740, 135, 460, 535]
|
|
1573
|
+
-->
|
|
1574
|
+
|
|
1575
|
+
## \u{1F4BB} Ph\xE2n T\xEDch C\u1EA5u Tr\xFAc M\xE3 Ngu\u1ED3n
|
|
1576
|
+
|
|
1577
|
+
<div class="columns-2">
|
|
1578
|
+
<div>
|
|
1579
|
+
|
|
1580
|
+
\`\`\`ts
|
|
1581
|
+
// Kh\u1EDFi t\u1EA1o quy tr\xECnh ki\u1EC3m tra d\u1EEF li\u1EC7u
|
|
1582
|
+
export async function processPayment(order: Order) {
|
|
1583
|
+
const isValid = await validateOrder(order);
|
|
1584
|
+
if (!isValid) throw new Error("Invalid");
|
|
1585
|
+
|
|
1586
|
+
const receipt = await chargeCard(order);
|
|
1587
|
+
return sendConfirmation(receipt);
|
|
1588
|
+
}
|
|
1589
|
+
\`\`\`
|
|
1590
|
+
|
|
1591
|
+
</div>
|
|
1592
|
+
<div>
|
|
1593
|
+
|
|
1594
|
+
### \u{1F50D} 3 \u0110i\u1EC3m C\u1EA7n Ch\xFA \xDD:
|
|
1595
|
+
1. **Ki\u1EC3m tra s\u1EDBm (Guard Clause):** Ki\u1EC3m tra t\xEDnh h\u1EE3p l\u1EC7 ngay d\xF2ng 3 \u0111\u1EC3 tr\xE1nh g\u1ECDi h\xE0m t\u1ED1n chi ph\xED.
|
|
1596
|
+
2. **B\u1EA5t \u0111\u1ED3ng b\u1ED9 (Async/Await):** \u0110\u1EA3m b\u1EA3o th\u1EE9 t\u1EF1 g\u1ECDi API t\xE0i ch\xEDnh chu\u1EA9n x\xE1c.
|
|
1597
|
+
3. **Ph\u1EA3n h\u1ED3i ng\u01B0\u1EDDi d\xF9ng:** Tr\u1EA3 v\u1EC1 bi\xEAn lai ho\xE0n ch\u1EC9nh sau khi giao d\u1ECBch th\xE0nh c\xF4ng.
|
|
1598
|
+
|
|
1599
|
+
</div>
|
|
1600
|
+
</div>
|
|
1601
|
+
|
|
1602
|
+
<!--
|
|
1603
|
+
Presenter Notes:
|
|
1604
|
+
- Y\xEAu c\u1EA7u h\u1ECDc vi\xEAn x\xE1c \u0111\u1ECBnh d\xF2ng code n\xE0o l\xE0 r\u1EE7i ro nh\u1EA5t n\u1EBFu k\u1EBFt n\u1ED1i m\u1EA1ng b\u1ECB r\u1EDBt.
|
|
1605
|
+
- Nh\u1EAFc l\u1EA1i nguy\xEAn t\u1EAFc Fail-Fast trong l\u1EADp tr\xECnh ph\xF2ng th\u1EE7.
|
|
1606
|
+
-->`
|
|
1607
|
+
},
|
|
1608
|
+
{
|
|
1609
|
+
id: "metrics-3-card",
|
|
1610
|
+
name: "L\u01B0\u1EDBi 3 Ch\u1EC9 S\u1ED1 (3-Metric Dashboard)",
|
|
1611
|
+
category: "metrics",
|
|
1612
|
+
description: "Hi\u1EC3n th\u1ECB 3 ch\u1EC9 s\u1ED1 quan tr\u1ECDng (KPIs, Benchmarks, Impact) d\u1EA1ng card n\u1ED5i b\u1EADt.",
|
|
1613
|
+
layout: {
|
|
1614
|
+
boxes: [
|
|
1615
|
+
{
|
|
1616
|
+
id: "heading",
|
|
1617
|
+
target: "h2",
|
|
1618
|
+
pos: [80, 50, 1120, 70],
|
|
1619
|
+
fontSize: "2rem",
|
|
1620
|
+
textAlign: "center"
|
|
1621
|
+
},
|
|
1622
|
+
{
|
|
1623
|
+
id: "metric-1",
|
|
1624
|
+
target: ".columns-3 > div:nth-child(1)",
|
|
1625
|
+
pos: [80, 165, 340, 485]
|
|
1626
|
+
},
|
|
1627
|
+
{
|
|
1628
|
+
id: "metric-2",
|
|
1629
|
+
target: ".columns-3 > div:nth-child(2)",
|
|
1630
|
+
pos: [470, 165, 340, 485]
|
|
1631
|
+
},
|
|
1632
|
+
{
|
|
1633
|
+
id: "metric-3",
|
|
1634
|
+
target: ".columns-3 > div:nth-child(3)",
|
|
1635
|
+
pos: [860, 165, 340, 485]
|
|
1636
|
+
}
|
|
1637
|
+
]
|
|
1638
|
+
},
|
|
1639
|
+
markdownSnippet: `<!-- layout:
|
|
1640
|
+
boxes:
|
|
1641
|
+
- id: heading
|
|
1642
|
+
target: h2
|
|
1643
|
+
pos: [80, 50, 1120, 70]
|
|
1644
|
+
fontSize: 2rem
|
|
1645
|
+
textAlign: center
|
|
1646
|
+
- id: metric-1
|
|
1647
|
+
target: '.columns-3 > div:nth-child(1)'
|
|
1648
|
+
pos: [80, 165, 340, 485]
|
|
1649
|
+
- id: metric-2
|
|
1650
|
+
target: '.columns-3 > div:nth-child(2)'
|
|
1651
|
+
pos: [470, 165, 340, 485]
|
|
1652
|
+
- id: metric-3
|
|
1653
|
+
target: '.columns-3 > div:nth-child(3)'
|
|
1654
|
+
pos: [860, 165, 340, 485]
|
|
1655
|
+
-->
|
|
1656
|
+
|
|
1657
|
+
## \u{1F4CA} 3 Ch\u1EC9 S\u1ED1 Th\xE0nh C\xF4ng C\u1EE7a D\u1EF1 \xC1n
|
|
1658
|
+
|
|
1659
|
+
<div class="columns-3">
|
|
1660
|
+
<div>
|
|
1661
|
+
|
|
1662
|
+
# \u26A1 99.9%
|
|
1663
|
+
### \u0110\u1ED9 S\u1EB5n S\xE0ng (Uptime)
|
|
1664
|
+
H\u1EC7 th\u1ED1ng v\u1EADn h\xE0nh li\xEAn t\u1EE5c 24/7 v\u1EDBi kh\u1EA3 n\u0103ng t\u1EF1 ph\u1EE5c h\u1ED3i l\u1ED7i t\u1EE9c th\xEC.
|
|
1665
|
+
|
|
1666
|
+
</div>
|
|
1667
|
+
<div>
|
|
1668
|
+
|
|
1669
|
+
# \u{1F680} 10x
|
|
1670
|
+
### T\u1ED1c \u0110\u1ED9 X\u1EED L\xFD
|
|
1671
|
+
R\xFAt ng\u1EAFn th\u1EDDi gian bi\xEAn d\u1ECBch v\xE0 ph\u1EA3n h\u1ED3i ng\u01B0\u1EDDi d\xF9ng t\u1EEB gi\xE2y xu\u1ED1ng mili-gi\xE2y.
|
|
1672
|
+
|
|
1673
|
+
</div>
|
|
1674
|
+
<div>
|
|
1675
|
+
|
|
1676
|
+
# \u{1F6E1}\uFE0F 0
|
|
1677
|
+
### L\u1ED7 H\u1ED5ng B\u1EA3o M\u1EADt
|
|
1678
|
+
\u0110\u1EA1t chu\u1EA9n \u0111\xE1nh gi\xE1 an to\xE0n OWASP v\xE0 m\xE3 h\xF3a d\u1EEF li\u1EC7u \u0111\u1EA7u cu\u1ED1i.
|
|
1679
|
+
|
|
1680
|
+
</div>
|
|
1681
|
+
</div>
|
|
1682
|
+
|
|
1683
|
+
<!--
|
|
1684
|
+
Presenter Notes:
|
|
1685
|
+
- Tr\xECnh b\xE0y l\u1EA7n l\u01B0\u1EE3t 3 m\u1EE5c ti\xEAu \u0111o l\u01B0\u1EDDng \u0111\u01B0\u1EE3c c\u1EE7a h\u1EC7 th\u1ED1ng.
|
|
1686
|
+
- Nh\u1EA5n m\u1EA1nh ch\u1EC9 s\u1ED1 th\u1EE9 2 l\xE0 tr\u1ECDng t\xE2m k\u1EF9 thu\u1EADt c\u1EE7a b\xE0i h\u1ECDc h\xF4m nay.
|
|
1687
|
+
-->`
|
|
1688
|
+
},
|
|
1689
|
+
{
|
|
1690
|
+
id: "process-flow",
|
|
1691
|
+
name: "S\u01A1 \u0110\u1ED3 Quy Tr\xECnh (Process / Flowchart)",
|
|
1692
|
+
category: "diagram",
|
|
1693
|
+
description: "Quy tr\xECnh nhi\u1EC1u b\u01B0\u1EDBc v\u1EDBi s\u01A1 \u0111\u1ED3 Mermaid tr\u1EF1c quan k\xE8m ghi ch\xFA t\xF3m t\u1EAFt.",
|
|
1694
|
+
layout: {
|
|
1695
|
+
boxes: [
|
|
1696
|
+
{
|
|
1697
|
+
id: "heading",
|
|
1698
|
+
target: "h2",
|
|
1699
|
+
pos: [80, 45, 1120, 65],
|
|
1700
|
+
fontSize: "1.9rem"
|
|
1701
|
+
},
|
|
1702
|
+
{
|
|
1703
|
+
id: "diagram-box",
|
|
1704
|
+
target: "pre.mermaid, .mermaid",
|
|
1705
|
+
pos: [80, 130, 1120, 310]
|
|
1706
|
+
},
|
|
1707
|
+
{
|
|
1708
|
+
id: "summary-box",
|
|
1709
|
+
target: "ul, p",
|
|
1710
|
+
pos: [80, 465, 1120, 205]
|
|
1711
|
+
}
|
|
1712
|
+
]
|
|
1713
|
+
},
|
|
1714
|
+
markdownSnippet: `<!-- layout:
|
|
1715
|
+
boxes:
|
|
1716
|
+
- id: heading
|
|
1717
|
+
target: h2
|
|
1718
|
+
pos: [80, 45, 1120, 65]
|
|
1719
|
+
fontSize: 1.9rem
|
|
1720
|
+
- id: diagram-box
|
|
1721
|
+
target: 'pre.mermaid, .mermaid'
|
|
1722
|
+
pos: [80, 130, 1120, 310]
|
|
1723
|
+
- id: summary-box
|
|
1724
|
+
target: 'ul, p'
|
|
1725
|
+
pos: [80, 465, 1120, 205]
|
|
1726
|
+
-->
|
|
1727
|
+
|
|
1728
|
+
## \u{1F504} Ki\u1EBFn Tr\xFAc Lu\u1ED3ng D\u1EEF Li\u1EC7u 4 B\u01B0\u1EDBc
|
|
1729
|
+
|
|
1730
|
+
\`\`\`mermaid
|
|
1731
|
+
graph LR
|
|
1732
|
+
A[1. Client Request] --> B[2. API Gateway]
|
|
1733
|
+
B --> C[3. Auth Service]
|
|
1734
|
+
C --> D[4. Database Cluster]
|
|
1735
|
+
style A fill:#0284c7,stroke:#38bdf8,stroke-width:2px,color:#fff
|
|
1736
|
+
style B fill:#1e293b,stroke:#64748b,stroke-width:2px,color:#fff
|
|
1737
|
+
style C fill:#059669,stroke:#34d399,stroke-width:2px,color:#fff
|
|
1738
|
+
style D fill:#7c3aed,stroke:#a78bfa,stroke-width:2px,color:#fff
|
|
1739
|
+
\`\`\`
|
|
1740
|
+
|
|
1741
|
+
- **Giai \u0111o\u1EA1n 1 & 2:** Ti\u1EBFp nh\u1EADn y\xEAu c\u1EA7u t\u1EEB client, \u0111i\u1EC1u h\u01B0\u1EDBng t\u1EA3i v\xE0 gi\u1EDBi h\u1EA1n t\u1EA7n su\u1EA5t (Rate Limiting).
|
|
1742
|
+
- **Giai \u0111o\u1EA1n 3 & 4:** X\xE1c th\u1EF1c token JWT ph\xE2n quy\u1EC1n tr\u01B0\u1EDBc khi truy v\u1EA5n d\u1EEF li\u1EC7u t\u1EEB DB Cluster.
|
|
1743
|
+
|
|
1744
|
+
<!--
|
|
1745
|
+
Presenter Notes:
|
|
1746
|
+
- Tr\u1ECF tr\u1EF1c ti\u1EBFp v\xE0o t\u1EEBng node c\u1EE7a s\u01A1 \u0111\u1ED3 Mermaid khi thuy\u1EBFt tr\xECnh.
|
|
1747
|
+
- \u0110\u1EB7t c\xE2u h\u1ECFi: "N\u1EBFu b\u01B0\u1EDBc 3 th\u1EA5t b\u1EA1i th\xEC b\u01B0\u1EDBc 4 c\xF3 \u0111\u01B0\u1EE3c g\u1ECDi kh\xF4ng?"
|
|
1748
|
+
-->`
|
|
1749
|
+
},
|
|
1750
|
+
{
|
|
1751
|
+
id: "quote-highlight",
|
|
1752
|
+
name: "Th\xF4ng \u0110i\u1EC7p Then Ch\u1ED1t (Quote / Highlight)",
|
|
1753
|
+
category: "quote",
|
|
1754
|
+
description: "Tr\xEDch d\u1EABn danh ng\xF4n ho\u1EB7c nguy\xEAn t\u1EAFc v\xE0ng c\u1EA7n ghi nh\u1EDB s\xE2u s\u1EAFc.",
|
|
1755
|
+
layout: {
|
|
1756
|
+
boxes: [
|
|
1757
|
+
{
|
|
1758
|
+
id: "heading",
|
|
1759
|
+
target: "h2",
|
|
1760
|
+
pos: [100, 100, 1080, 80],
|
|
1761
|
+
fontSize: "2.2rem",
|
|
1762
|
+
textAlign: "center"
|
|
1763
|
+
},
|
|
1764
|
+
{
|
|
1765
|
+
id: "quote-box",
|
|
1766
|
+
target: "blockquote",
|
|
1767
|
+
pos: [140, 230, 1e3, 360],
|
|
1768
|
+
fontSize: "1.4rem"
|
|
1769
|
+
}
|
|
1770
|
+
]
|
|
1771
|
+
},
|
|
1772
|
+
markdownSnippet: `<!-- layout:
|
|
1773
|
+
boxes:
|
|
1774
|
+
- id: heading
|
|
1775
|
+
target: h2
|
|
1776
|
+
pos: [100, 100, 1080, 80]
|
|
1777
|
+
fontSize: 2.2rem
|
|
1778
|
+
textAlign: center
|
|
1779
|
+
- id: quote-box
|
|
1780
|
+
target: blockquote
|
|
1781
|
+
pos: [140, 230, 1000, 360]
|
|
1782
|
+
fontSize: 1.4rem
|
|
1783
|
+
-->
|
|
1784
|
+
|
|
1785
|
+
## \u{1F4A1} Nguy\xEAn T\u1EAFc Thi\u1EBFt K\u1EBF C\u1ED1t L\xF5i
|
|
1786
|
+
|
|
1787
|
+
> "S\u1EF1 \u0111\u01A1n gi\u1EA3n l\xE0 \u0111i\u1EC1u ki\u1EC7n ti\xEAn quy\u1EBFt cho \u0111\u1ED9 tin c\u1EADy. M\u1ED9t ki\u1EBFn tr\xFAc t\u1ED1t l\xE0 khi b\u1EA5t k\u1EF3 l\u1EADp tr\xECnh vi\xEAn m\u1EDBi n\xE0o c\u0169ng c\xF3 th\u1EC3 \u0111\u1ECDc hi\u1EC3u lu\u1ED3ng ho\u1EA1t \u0111\u1ED9ng ch\u1EC9 sau 15 ph\xFAt."
|
|
1788
|
+
>
|
|
1789
|
+
> \u2014 **Edsger W. Dijkstra**
|
|
1790
|
+
|
|
1791
|
+
<!--
|
|
1792
|
+
Presenter Notes:
|
|
1793
|
+
- D\u1EEBng l\u1EA1i 5 gi\xE2y im l\u1EB7ng \u0111\u1EC3 c\u1EA3 l\u1EDBp c\xF9ng suy ng\u1EABm v\u1EC1 th\xF4ng \u0111i\u1EC7p.
|
|
1794
|
+
- Y\xEAu c\u1EA7u m\u1ED9t h\u1ECDc vi\xEAn chia s\u1EBB suy ngh\u0129 v\u1EC1 c\xE1ch \xE1p d\u1EE5ng nguy\xEAn t\u1EAFc n\xE0y v\xE0o b\xE0i t\u1EADp l\u1EDBn.
|
|
1795
|
+
-->`
|
|
1796
|
+
},
|
|
1797
|
+
{
|
|
1798
|
+
id: "quiz-checkpoint",
|
|
1799
|
+
name: "Tr\u1EAFc Nghi\u1EC7m T\u01B0\u01A1ng T\xE1c (Quiz Checkpoint)",
|
|
1800
|
+
category: "assessment",
|
|
1801
|
+
description: "C\xE2u h\u1ECFi t\u01B0\u01A1ng t\xE1c Formative Checkpoint k\xE8m c\xE1c l\u1EF1a ch\u1ECDn A/B/C/D \u0111\u1EC3 l\u1EDBp b\xECnh ch\u1ECDn.",
|
|
1802
|
+
layout: {
|
|
1803
|
+
boxes: [
|
|
1804
|
+
{
|
|
1805
|
+
id: "heading",
|
|
1806
|
+
target: "h2",
|
|
1807
|
+
pos: [80, 45, 1120, 65],
|
|
1808
|
+
fontSize: "1.9rem"
|
|
1809
|
+
},
|
|
1810
|
+
{
|
|
1811
|
+
id: "question-callout",
|
|
1812
|
+
target: "blockquote",
|
|
1813
|
+
pos: [80, 130, 1120, 110],
|
|
1814
|
+
fontSize: "1.2rem"
|
|
1815
|
+
},
|
|
1816
|
+
{
|
|
1817
|
+
id: "options-list",
|
|
1818
|
+
target: "ul",
|
|
1819
|
+
pos: [80, 260, 1120, 390],
|
|
1820
|
+
fontSize: "1.15rem"
|
|
1821
|
+
}
|
|
1822
|
+
]
|
|
1823
|
+
},
|
|
1824
|
+
markdownSnippet: `<!-- layout:
|
|
1825
|
+
boxes:
|
|
1826
|
+
- id: heading
|
|
1827
|
+
target: h2
|
|
1828
|
+
pos: [80, 45, 1120, 65]
|
|
1829
|
+
fontSize: 1.9rem
|
|
1830
|
+
- id: question-callout
|
|
1831
|
+
target: blockquote
|
|
1832
|
+
pos: [80, 130, 1120, 110]
|
|
1833
|
+
fontSize: 1.2rem
|
|
1834
|
+
- id: options-list
|
|
1835
|
+
target: ul
|
|
1836
|
+
pos: [80, 260, 1120, 390]
|
|
1837
|
+
fontSize: 1.15rem
|
|
1838
|
+
-->
|
|
1839
|
+
|
|
1840
|
+
## \u{1F3AF} Ki\u1EC3m Tra Nhanh: B\u1EA1n \u0110\xE3 Hi\u1EC3u \u0110\xFAng?
|
|
1841
|
+
|
|
1842
|
+
> **C\xE2u h\u1ECFi:** Trong ki\u1EBFn tr\xFAc Microservices, c\u01A1 ch\u1EBF n\xE0o gi\xFAp \u0111\u1EA3m b\u1EA3o tin nh\u1EAFn kh\xF4ng b\u1ECB th\u1EA5t l\u1EA1c khi d\u1ECBch v\u1EE5 nh\u1EADn g\u1EB7p s\u1EF1 c\u1ED1 t\u1EA1m th\u1EDDi?
|
|
1843
|
+
|
|
1844
|
+
- **A.** G\u1ECDi API HTTP tr\u1EF1c ti\u1EBFp v\u1EDBi timeout ng\u1EAFn
|
|
1845
|
+
- **B.** S\u1EED d\u1EE5ng Message Queue (H\xE0ng \u0111\u1EE3i tin nh\u1EAFn) c\xF3 c\u01A1 ch\u1EBF Retry & DLQ
|
|
1846
|
+
- **C.** L\u01B0u t\u1EA1m v\xE0o b\u1ED9 nh\u1EDB RAM c\u1EE7a m\xE1y kh\xE1ch (Client In-Memory Storage)
|
|
1847
|
+
- **D.** B\u1ECF qua tin nh\u1EAFn b\u1ECB l\u1ED7i v\xE0 th\xF4ng b\xE1o ng\u01B0\u1EDDi d\xF9ng g\u1EEDi l\u1EA1i t\u1EEB \u0111\u1EA7u
|
|
1848
|
+
|
|
1849
|
+
<!--
|
|
1850
|
+
Presenter Notes:
|
|
1851
|
+
- D\xE0nh 30 gi\xE2y cho h\u1ECDc vi\xEAn gi\u01A1 tay ho\u1EB7c b\xECnh ch\u1ECDn \u0111\xE1p \xE1n.
|
|
1852
|
+
- \u0110\xE1p \xE1n ch\xEDnh x\xE1c: B.
|
|
1853
|
+
- Gi\u1EA3i th\xEDch v\xEC sao \u0111\xE1p \xE1n A v\xE0 C ti\u1EC1m \u1EA9n nguy c\u01A1 m\u1EA5t m\xE1t d\u1EEF li\u1EC7u nghi\xEAm tr\u1ECDng.
|
|
1854
|
+
-->`
|
|
1855
|
+
},
|
|
1856
|
+
{
|
|
1857
|
+
id: "tiered-practice-3cards",
|
|
1858
|
+
name: "Th\u1EF1c H\xE0nh Ph\xE2n H\xF3a (Tiered Practice)",
|
|
1859
|
+
category: "content",
|
|
1860
|
+
description: "3 c\u1EA5p b\u1EADc nhi\u1EC7m v\u1EE5 \u0110\u1ED3ng - B\u1EA1c - V\xE0ng \u0111\u1EC3 h\u1ECDc sinh t\u1EF1 ch\u1ECDn m\u1EE9c th\u1EED th\xE1ch ph\xF9 h\u1EE3p.",
|
|
1861
|
+
layout: {
|
|
1862
|
+
boxes: [
|
|
1863
|
+
{
|
|
1864
|
+
id: "heading",
|
|
1865
|
+
target: "h2",
|
|
1866
|
+
pos: [80, 45, 1120, 65],
|
|
1867
|
+
fontSize: "1.9rem"
|
|
1868
|
+
},
|
|
1869
|
+
{
|
|
1870
|
+
id: "tier-bronze",
|
|
1871
|
+
target: ".columns-3 > div:nth-child(1)",
|
|
1872
|
+
pos: [80, 140, 350, 520]
|
|
1873
|
+
},
|
|
1874
|
+
{
|
|
1875
|
+
id: "tier-silver",
|
|
1876
|
+
target: ".columns-3 > div:nth-child(2)",
|
|
1877
|
+
pos: [465, 140, 350, 520]
|
|
1878
|
+
},
|
|
1879
|
+
{
|
|
1880
|
+
id: "tier-gold",
|
|
1881
|
+
target: ".columns-3 > div:nth-child(3)",
|
|
1882
|
+
pos: [850, 140, 350, 520]
|
|
1883
|
+
}
|
|
1884
|
+
]
|
|
1885
|
+
},
|
|
1886
|
+
markdownSnippet: `<!-- layout:
|
|
1887
|
+
boxes:
|
|
1888
|
+
- id: heading
|
|
1889
|
+
target: h2
|
|
1890
|
+
pos: [80, 45, 1120, 65]
|
|
1891
|
+
fontSize: 1.9rem
|
|
1892
|
+
- id: tier-bronze
|
|
1893
|
+
target: '.columns-3 > div:nth-child(1)'
|
|
1894
|
+
pos: [80, 140, 350, 520]
|
|
1895
|
+
- id: tier-silver
|
|
1896
|
+
target: '.columns-3 > div:nth-child(2)'
|
|
1897
|
+
pos: [465, 140, 350, 520]
|
|
1898
|
+
- id: tier-gold
|
|
1899
|
+
target: '.columns-3 > div:nth-child(3)'
|
|
1900
|
+
pos: [850, 140, 350, 520]
|
|
1901
|
+
-->
|
|
1902
|
+
|
|
1903
|
+
## \u{1F6E0}\uFE0F Th\u1EED Th\xE1ch Ph\xE2n H\xF3a 3 C\u1EA5p \u0110\u1ED9 (Tiered Lab)
|
|
1904
|
+
|
|
1905
|
+
<div class="columns-3">
|
|
1906
|
+
<div>
|
|
1907
|
+
|
|
1908
|
+
### \u{1F949} H\u1EA1ng \u0110\u1ED3ng (Bronze)
|
|
1909
|
+
- Vi\u1EBFt h\xE0m x\u1EED l\xFD c\u01A1 b\u1EA3n
|
|
1910
|
+
- V\u01B0\u1EE3t qua 3 test case m\u1EABu
|
|
1911
|
+
- *M\u1EE5c ti\xEAu: \u0110\u1EA1t chu\u1EA9n ki\u1EBFn th\u1EE9c c\u01A1 b\u1EA3n*
|
|
1912
|
+
|
|
1913
|
+
</div>
|
|
1914
|
+
<div>
|
|
1915
|
+
|
|
1916
|
+
### \u{1F948} H\u1EA1ng B\u1EA1c (Silver)
|
|
1917
|
+
- Th\xEAm ki\u1EC3m tra \u0111i\u1EC1u ki\u1EC7n bi\xEAn
|
|
1918
|
+
- T\u1ED1i \u01B0u \u0111\u1ED9 ph\u1EE9c t\u1EA1p thu\u1EADt to\xE1n O(N)
|
|
1919
|
+
- *M\u1EE5c ti\xEAu: V\u1EEFng v\xE0ng k\u1EF9 n\u0103ng th\u1EF1c chi\u1EBFn*
|
|
1920
|
+
|
|
1921
|
+
</div>
|
|
1922
|
+
<div>
|
|
1923
|
+
|
|
1924
|
+
### \u{1F947} H\u1EA1ng V\xE0ng (Gold)
|
|
1925
|
+
- Thi\u1EBFt k\u1EBF t\xEDnh n\u0103ng m\u1EDF r\u1ED9ng
|
|
1926
|
+
- Vi\u1EBFt Unit Test t\u1EF1 \u0111\u1ED9ng bao ph\u1EE7 90%
|
|
1927
|
+
- *M\u1EE5c ti\xEAu: S\xE1ng t\u1EA1o & b\u1EE9t ph\xE1*
|
|
1928
|
+
|
|
1929
|
+
</div>
|
|
1930
|
+
</div>
|
|
1931
|
+
|
|
1932
|
+
<!--
|
|
1933
|
+
Presenter Notes:
|
|
1934
|
+
- To\xE0n b\u1ED9 l\u1EDBp \u0111\u1EC1u b\u1EAFt \u0111\u1EA7u t\u1EEB H\u1EA1ng \u0110\u1ED3ng \u0111\u1EC3 \u0111\u1EA3m b\u1EA3o hi\u1EC3u b\xE0i.
|
|
1935
|
+
- Khuy\u1EBFn kh\xEDch h\u1ECDc vi\xEAn \u0111\xE3 xong Bronze ch\u1EE7 \u0111\u1ED9ng th\u1EED s\u1EE9c v\u1EDBi Silver v\xE0 Gold.
|
|
1936
|
+
-->`
|
|
1937
|
+
},
|
|
1938
|
+
{
|
|
1939
|
+
id: "agenda-timeline",
|
|
1940
|
+
name: "L\u1ED9 Tr\xECnh Bu\u1ED5i H\u1ECDc (Agenda Table)",
|
|
1941
|
+
category: "content",
|
|
1942
|
+
description: "B\u1EA3ng l\u1ED9 tr\xECnh th\u1EDDi gian chi ti\u1EBFt theo t\u1EEBng giai \u0111o\u1EA1n v\xE0 s\u1EA3n ph\u1EA9m \u0111\u1EA7u ra.",
|
|
1943
|
+
layout: {
|
|
1944
|
+
boxes: [
|
|
1945
|
+
{
|
|
1946
|
+
id: "heading",
|
|
1947
|
+
target: "h2",
|
|
1948
|
+
pos: [80, 45, 1120, 65],
|
|
1949
|
+
fontSize: "1.9rem"
|
|
1950
|
+
},
|
|
1951
|
+
{
|
|
1952
|
+
id: "table-box",
|
|
1953
|
+
target: "table",
|
|
1954
|
+
pos: [80, 140, 1120, 520]
|
|
1955
|
+
}
|
|
1956
|
+
]
|
|
1957
|
+
},
|
|
1958
|
+
markdownSnippet: `<!-- layout:
|
|
1959
|
+
boxes:
|
|
1960
|
+
- id: heading
|
|
1961
|
+
target: h2
|
|
1962
|
+
pos: [80, 45, 1120, 65]
|
|
1963
|
+
fontSize: 1.9rem
|
|
1964
|
+
- id: table-box
|
|
1965
|
+
target: table
|
|
1966
|
+
pos: [80, 140, 1120, 520]
|
|
1967
|
+
-->
|
|
1968
|
+
|
|
1969
|
+
## \u{1F5FA}\uFE0F L\u1ED9 Tr\xECnh Ho\u1EA1t \u0110\u1ED9ng & M\u1EE5c Ti\xEAu
|
|
1970
|
+
|
|
1971
|
+
| Th\u1EDDi l\u01B0\u1EE3ng | Giai \u0111o\u1EA1n | Tr\u1ECDng t\xE2m ho\u1EA1t \u0111\u1ED9ng | K\u1EBFt qu\u1EA3 \u0111\u1EA1t \u0111\u01B0\u1EE3c |
|
|
1972
|
+
|:---:|---|---|---|
|
|
1973
|
+
| **05'** | Kh\u1EDFi \u0111\u1ED9ng | T\xECnh hu\u1ED1ng th\u1EF1c t\u1EBF & Th\u1EED th\xE1ch | Nh\u1EADn di\u1EC7n b\xE0i to\xE1n c\u1EA7n gi\u1EA3i |
|
|
1974
|
+
| **15'** | Kh\xE1m ph\xE1 | Ph\xE2n t\xEDch 2 kh\xE1i ni\u1EC7m c\u1ED1t l\xF5i | Hi\u1EC3u b\u1EA3n ch\u1EA5t thu\u1EADt to\xE1n |
|
|
1975
|
+
| **20'** | Th\u1EF1c h\xE0nh | Code Lab t\u1EA1i m\xE1y theo 3 b\u1EADc | Ho\xE0n th\xE0nh t\u1ED1i thi\u1EC3u H\u1EA1ng \u0110\u1ED3ng |
|
|
1976
|
+
| **05'** | T\u1ED5ng k\u1EBFt | Tr\u1EAFc nghi\u1EC7m ph\u1EA3n x\u1EA1 & \u0110\xE1nh gi\xE1 | H\u1EC7 th\u1ED1ng h\xF3a to\xE0n b\u1ED9 ki\u1EBFn th\u1EE9c |
|
|
1977
|
+
|
|
1978
|
+
<!--
|
|
1979
|
+
Presenter Notes:
|
|
1980
|
+
- Gi\u1EDBi thi\u1EC7u t\u1ED5ng quan \u0111\u1EC3 h\u1ECDc vi\xEAn bi\u1EBFt r\xF5 c\u1EA5u tr\xFAc bu\u1ED5i h\u1ECDc v\xE0 ch\u1EE7 \u0111\u1ED9ng ph\xE2n b\u1ED5 th\u1EDDi gian.
|
|
1981
|
+
-->`
|
|
1982
|
+
},
|
|
1983
|
+
{
|
|
1984
|
+
id: "takeaways-summary",
|
|
1985
|
+
name: "T\u1ED5ng K\u1EBFt C\u1ED1t L\xF5i (Key Takeaways)",
|
|
1986
|
+
category: "content",
|
|
1987
|
+
description: "Slide k\u1EBFt lu\u1EADn v\u1EDBi 3 b\xE0i h\u1ECDc \u0111\u1EAFt gi\xE1 nh\u1EA5t v\xE0 h\xE0nh \u0111\u1ED9ng ti\u1EBFp theo.",
|
|
1988
|
+
layout: {
|
|
1989
|
+
boxes: [
|
|
1990
|
+
{
|
|
1991
|
+
id: "heading",
|
|
1992
|
+
target: "h2",
|
|
1993
|
+
pos: [80, 50, 1120, 70],
|
|
1994
|
+
fontSize: "2rem"
|
|
1995
|
+
},
|
|
1996
|
+
{
|
|
1997
|
+
id: "list-box",
|
|
1998
|
+
target: "ol",
|
|
1999
|
+
pos: [80, 150, 1120, 340],
|
|
2000
|
+
fontSize: "1.25rem"
|
|
2001
|
+
},
|
|
2002
|
+
{
|
|
2003
|
+
id: "callout-next",
|
|
2004
|
+
target: "blockquote",
|
|
2005
|
+
pos: [80, 520, 1120, 140],
|
|
2006
|
+
fontSize: "1.1rem"
|
|
2007
|
+
}
|
|
2008
|
+
]
|
|
2009
|
+
},
|
|
2010
|
+
markdownSnippet: `<!-- layout:
|
|
2011
|
+
boxes:
|
|
2012
|
+
- id: heading
|
|
2013
|
+
target: h2
|
|
2014
|
+
pos: [80, 50, 1120, 70]
|
|
2015
|
+
fontSize: 2rem
|
|
2016
|
+
- id: list-box
|
|
2017
|
+
target: ol
|
|
2018
|
+
pos: [80, 150, 1120, 340]
|
|
2019
|
+
fontSize: 1.25rem
|
|
2020
|
+
- id: callout-next
|
|
2021
|
+
target: blockquote
|
|
2022
|
+
pos: [80, 520, 1120, 140]
|
|
2023
|
+
fontSize: 1.1rem
|
|
2024
|
+
-->
|
|
2025
|
+
|
|
2026
|
+
## \u{1F3C1} 3 \u0110i\u1EC1u C\u1EA7n Nh\u1EDB Sau Bu\u1ED5i H\u1ECDc
|
|
2027
|
+
|
|
2028
|
+
1. **Hi\u1EC3u b\u1EA3n ch\u1EA5t tr\u01B0\u1EDBc khi vi\u1EBFt m\xE3:** Thu\u1EADt to\xE1n \u0111\xFAng lu\xF4n quan tr\u1ECDng h\u01A1n c\xFA ph\xE1p b\xF3ng b\u1EA9y.
|
|
2029
|
+
2. **Ki\u1EC3m so\xE1t \u0111i\u1EC1u ki\u1EC7n bi\xEAn:** 80% l\u1ED7i h\u1EC7 th\u1ED1ng ph\xE1t sinh t\u1EEB c\xE1c d\u1EEF li\u1EC7u \u0111\u1EA7u v\xE0o kh\xF4ng l\u01B0\u1EDDng tr\u01B0\u1EDBc.
|
|
2030
|
+
3. **Thi\u1EBFt k\u1EBF h\u01B0\u1EDBng m\xF4-\u0111un:** Chia nh\u1ECF v\u1EA5n \u0111\u1EC1 l\u1EDBn th\xE0nh c\xE1c h\xE0m \u0111\u1ED9c l\u1EADp d\u1EC5 ki\u1EC3m th\u1EED.
|
|
2031
|
+
|
|
2032
|
+
> \u{1F4DD} **Nhi\u1EC7m v\u1EE5 v\u1EC1 nh\xE0:** Ho\xE0n thi\u1EC7n b\xE0i th\u1EF1c h\xE0nh H\u1EA1ng B\u1EA1c v\xE0 chu\u1EA9n b\u1ECB c\xE2u h\u1ECFi cho bu\u1ED5i Review tu\u1EA7n t\u1EDBi.
|
|
2033
|
+
|
|
2034
|
+
<!--
|
|
2035
|
+
Presenter Notes:
|
|
2036
|
+
- T\xF3m t\u1EAFt s\xFAc t\xEDch trong 2 ph\xFAt cu\u1ED1i bu\u1ED5i.
|
|
2037
|
+
- Tuy\xEAn d\u01B0\u01A1ng c\xE1c b\u1EA1n c\xF3 gi\u1EA3i ph\xE1p xu\u1EA5t s\u1EAFc trong ph\u1EA7n th\u1EF1c h\xE0nh.
|
|
2038
|
+
-->`
|
|
2039
|
+
}
|
|
2040
|
+
];
|
|
2041
|
+
function getSlideLayoutPresetById(id) {
|
|
2042
|
+
return SLIDE_LAYOUT_PRESETS.find((p) => p.id === id);
|
|
2043
|
+
}
|
|
2044
|
+
function getSlideLayoutPresetsByCategory(category) {
|
|
2045
|
+
return SLIDE_LAYOUT_PRESETS.filter((p) => p.category === category);
|
|
2046
|
+
}
|
|
1164
2047
|
function renderMarp(markdown, containerId = "marp-container", enableClicks = false) {
|
|
1165
2048
|
const cleanSource = extractMarpSource(markdown);
|
|
1166
2049
|
const { markdown: clickProcessed, totalClicks } = enableClicks ? preprocessClicks(cleanSource) : { markdown: cleanSource, totalClicks: 0 };
|
|
@@ -1170,12 +2053,12 @@ function renderMarp(markdown, containerId = "marp-container", enableClicks = fal
|
|
|
1170
2053
|
html: true,
|
|
1171
2054
|
emoji: { unicode: true }
|
|
1172
2055
|
});
|
|
1173
|
-
const { html, css } = marp.render(arrowsConverted);
|
|
2056
|
+
const { html, css, comments } = marp.render(arrowsConverted);
|
|
1174
2057
|
if (enableClicks && totalClicks > 0) {
|
|
1175
2058
|
const { html: processedHtml, totalClicks: finalClicks } = postprocessClicks(html);
|
|
1176
|
-
return { html: processedHtml, css, maxClicks: finalClicks };
|
|
2059
|
+
return { html: processedHtml, css, comments: comments ?? [], maxClicks: finalClicks };
|
|
1177
2060
|
}
|
|
1178
|
-
return { html, css, maxClicks: 0 };
|
|
2061
|
+
return { html, css, comments: comments ?? [], maxClicks: 0 };
|
|
1179
2062
|
}
|
|
1180
2063
|
function countSlides(html) {
|
|
1181
2064
|
const matches = html.match(/<svg[^>]*data-marpit-svg/g);
|
|
@@ -1185,47 +2068,54 @@ function renderSlide(markdown, options) {
|
|
|
1185
2068
|
const containerId = options?.containerId ?? "marp-container";
|
|
1186
2069
|
const theme = resolveTheme(options?.theme ?? "dark");
|
|
1187
2070
|
const enableClicks = options?.enableClicks ?? false;
|
|
1188
|
-
const { html, css, maxClicks } = renderMarp(markdown, containerId, enableClicks);
|
|
2071
|
+
const { html, css, comments, maxClicks } = renderMarp(markdown, containerId, enableClicks);
|
|
1189
2072
|
const themeCss = generateScopedCss2(theme, containerId);
|
|
1190
2073
|
const slideCount = countSlides(html);
|
|
1191
2074
|
const mermaidBlocks = options?.enableMermaid !== false ? detectMermaidBlocks(html) : [];
|
|
1192
2075
|
const clickCss = enableClicks ? generateClickCss({ containerId }) : "";
|
|
2076
|
+
const { layouts, notes } = extractLayoutsAndNotes(comments);
|
|
2077
|
+
const layoutCss = generateLayoutCss(layouts, containerId);
|
|
1193
2078
|
return {
|
|
1194
2079
|
html,
|
|
1195
2080
|
css,
|
|
1196
2081
|
themeCss,
|
|
2082
|
+
layoutCss,
|
|
1197
2083
|
slideCount,
|
|
1198
2084
|
mermaidBlocks,
|
|
1199
2085
|
clickCss,
|
|
1200
|
-
maxClicks
|
|
2086
|
+
maxClicks,
|
|
2087
|
+
notes,
|
|
2088
|
+
layouts
|
|
1201
2089
|
};
|
|
1202
2090
|
}
|
|
1203
2091
|
async function renderSlideAsync(markdown, options) {
|
|
1204
2092
|
const containerId = options?.containerId ?? "marp-container";
|
|
1205
2093
|
const theme = resolveTheme(options?.theme ?? "dark");
|
|
1206
2094
|
const enableClicks = options?.enableClicks ?? false;
|
|
1207
|
-
const { html, css, maxClicks } = renderMarp(markdown, containerId, enableClicks);
|
|
2095
|
+
const { html, css, comments, maxClicks } = renderMarp(markdown, containerId, enableClicks);
|
|
1208
2096
|
const themeCss = generateScopedCss2(theme, containerId);
|
|
1209
2097
|
const slideCount = countSlides(html);
|
|
1210
2098
|
const mermaidBlocks = options?.enableMermaid !== false ? detectMermaidBlocks(html) : [];
|
|
1211
2099
|
const clickCss = enableClicks ? generateClickCss({ containerId }) : "";
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
}
|
|
2100
|
+
const { layouts, notes } = extractLayoutsAndNotes(comments);
|
|
2101
|
+
const layoutCss = generateLayoutCss(layouts, containerId);
|
|
2102
|
+
const enableShiki = options?.enableShiki ?? true;
|
|
2103
|
+
const processedHtml = enableShiki ? await highlightCodeBlocks(html, theme.name) : html;
|
|
1217
2104
|
return {
|
|
1218
2105
|
html,
|
|
1219
2106
|
css,
|
|
1220
2107
|
themeCss,
|
|
2108
|
+
layoutCss,
|
|
2109
|
+
processedHtml,
|
|
1221
2110
|
slideCount,
|
|
1222
2111
|
mermaidBlocks,
|
|
1223
2112
|
clickCss,
|
|
1224
2113
|
maxClicks,
|
|
1225
|
-
|
|
2114
|
+
notes,
|
|
2115
|
+
layouts
|
|
1226
2116
|
};
|
|
1227
2117
|
}
|
|
1228
2118
|
|
|
1229
|
-
export { convertTextArrows, countSlides, darkThemeTokens, defineTheme, detectMermaidBlocks, extractMarpSource, extractMaxClicks, generateClickCss, generateScopedCss, generateScopedCss2, getHighlighter, getThemeTokens, highlightCodeBlocks, lightThemeTokens, navyThemeTokens, postprocessClicks, preprocessClicks, renderMarp, renderMermaidDiagrams, renderSlide, renderSlideAsync, resolveTheme, sanitizeMermaidCode };
|
|
1230
|
-
//# sourceMappingURL=chunk-
|
|
1231
|
-
//# sourceMappingURL=chunk-
|
|
2119
|
+
export { SLIDE_LAYOUT_PRESETS, convertTextArrows, countSlides, darkThemeTokens, defineTheme, detectMermaidBlocks, extractLayoutsAndNotes, extractMarpSource, extractMaxClicks, filterPresenterNotes, generateClickCss, generateLayoutCss, generateScopedCss, generateScopedCss2, getHighlighter, getSlideLayoutPresetById, getSlideLayoutPresetsByCategory, getThemeTokens, highlightCodeBlocks, isLayoutComment, lightThemeTokens, navyThemeTokens, parseLayoutFromComment, postprocessClicks, preprocessClicks, renderMarp, renderMermaidDiagrams, renderSlide, renderSlideAsync, resolveTheme, sanitizeMermaidCode, serializeLayoutToComment, splitMarkdownSlides, updateSlideLayoutInMarkdown };
|
|
2120
|
+
//# sourceMappingURL=chunk-PAHIW5IX.js.map
|
|
2121
|
+
//# sourceMappingURL=chunk-PAHIW5IX.js.map
|