pxt-core 8.5.13 → 8.5.14
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/built/cli.js +2 -1
- package/built/pxt.js +162 -13
- package/built/pxtlib.d.ts +17 -3
- package/built/pxtlib.js +160 -12
- package/built/target.js +1 -1
- package/built/web/main.js +1 -1
- package/built/web/pxtapp.js +1 -1
- package/built/web/pxtembed.js +1 -1
- package/built/web/pxtlib.js +1 -1
- package/built/web/pxtworker.js +1 -1
- package/built/web/rtlsemantic.css +1 -1
- package/built/web/semantic.css +1 -1
- package/built/web/skillmap/js/{main.3008f5a9.js → main.ae646473.js} +2 -2
- package/localtypings/pxtarget.d.ts +35 -0
- package/package.json +1 -1
- package/theme/tutorial-sidebar.less +62 -0
- package/webapp/public/skillmap.html +1 -1
package/built/cli.js
CHANGED
|
@@ -5497,7 +5497,7 @@ function internalCacheUsedBlocksAsync() {
|
|
|
5497
5497
|
const decompiled = pxtc.decompileSnippets(pxtc.getTSProgram(opts), opts, false);
|
|
5498
5498
|
if ((decompiled === null || decompiled === void 0 ? void 0 : decompiled.length) > 0) {
|
|
5499
5499
|
// scrape block IDs matching <block type="block_id">
|
|
5500
|
-
let builtInfo = builtTututorialInfo[hash] || { usedBlocks: {}, snippetBlocks: {} };
|
|
5500
|
+
let builtInfo = builtTututorialInfo[hash] || { usedBlocks: {}, snippetBlocks: {}, highlightBlocks: {} };
|
|
5501
5501
|
const blockIdRegex = /<\s*block(?:[^>]*)? type="([^ ]*)"/ig;
|
|
5502
5502
|
for (let i = 0; i < decompiled.length; i++) {
|
|
5503
5503
|
const blocksXml = decompiled[i];
|
|
@@ -5507,6 +5507,7 @@ function internalCacheUsedBlocksAsync() {
|
|
|
5507
5507
|
builtInfo.snippetBlocks[snippetHash] = {};
|
|
5508
5508
|
builtInfo.snippetBlocks[snippetHash][m1] = 1;
|
|
5509
5509
|
builtInfo.usedBlocks[m1] = 1;
|
|
5510
|
+
//TODO: Fill builtInfo.HighlightedBlocks
|
|
5510
5511
|
return m0;
|
|
5511
5512
|
});
|
|
5512
5513
|
}
|
package/built/pxt.js
CHANGED
|
@@ -100364,7 +100364,8 @@ var ts;
|
|
|
100364
100364
|
res.tutorialInfo[key] = {
|
|
100365
100365
|
hash: built.hash,
|
|
100366
100366
|
usedBlocks: Object.assign({}, built.usedBlocks),
|
|
100367
|
-
snippetBlocks: Object.assign({}, built.snippetBlocks)
|
|
100367
|
+
snippetBlocks: Object.assign({}, built.snippetBlocks),
|
|
100368
|
+
highlightBlocks: Object.assign({}, built.highlightBlocks)
|
|
100368
100369
|
};
|
|
100369
100370
|
}
|
|
100370
100371
|
}
|
|
@@ -103215,7 +103216,7 @@ var pxt;
|
|
|
103215
103216
|
}
|
|
103216
103217
|
static createAsync() {
|
|
103217
103218
|
function openAsync() {
|
|
103218
|
-
const idbWrapper = new pxt.BrowserUtils.IDBWrapper(TutorialInfoIndexedDb.dbName(),
|
|
103219
|
+
const idbWrapper = new pxt.BrowserUtils.IDBWrapper(TutorialInfoIndexedDb.dbName(), 3, (ev, r) => {
|
|
103219
103220
|
const db = r.result;
|
|
103220
103221
|
db.createObjectStore(TutorialInfoIndexedDb.TABLE, { keyPath: TutorialInfoIndexedDb.KEYPATH });
|
|
103221
103222
|
}, () => {
|
|
@@ -103245,13 +103246,13 @@ var pxt;
|
|
|
103245
103246
|
return undefined;
|
|
103246
103247
|
});
|
|
103247
103248
|
}
|
|
103248
|
-
setAsync(filename, snippets, code, branch) {
|
|
103249
|
+
setAsync(filename, snippets, code, highlights, branch) {
|
|
103249
103250
|
pxt.perf.measureStart("tutorial info db setAsync");
|
|
103250
103251
|
const key = getTutorialInfoKey(filename, branch);
|
|
103251
103252
|
const hash = getTutorialCodeHash(code);
|
|
103252
|
-
return this.setWithHashAsync(filename, snippets, hash);
|
|
103253
|
+
return this.setWithHashAsync(filename, snippets, hash, highlights);
|
|
103253
103254
|
}
|
|
103254
|
-
setWithHashAsync(filename, snippets, hash, branch) {
|
|
103255
|
+
setWithHashAsync(filename, snippets, hash, highlights, branch) {
|
|
103255
103256
|
pxt.perf.measureStart("tutorial info db setAsync");
|
|
103256
103257
|
const key = getTutorialInfoKey(filename, branch);
|
|
103257
103258
|
const blocks = {};
|
|
@@ -103265,7 +103266,8 @@ var pxt;
|
|
|
103265
103266
|
time: pxt.Util.now(),
|
|
103266
103267
|
hash,
|
|
103267
103268
|
snippets,
|
|
103268
|
-
blocks
|
|
103269
|
+
blocks,
|
|
103270
|
+
highlightBlocks: highlights,
|
|
103269
103271
|
};
|
|
103270
103272
|
return this.db.setAsync(TutorialInfoIndexedDb.TABLE, entry)
|
|
103271
103273
|
.then(() => {
|
|
@@ -111401,6 +111403,125 @@ var pxt;
|
|
|
111401
111403
|
})(storage = pxt.storage || (pxt.storage = {}));
|
|
111402
111404
|
})(pxt || (pxt = {}));
|
|
111403
111405
|
var pxt;
|
|
111406
|
+
(function (pxt) {
|
|
111407
|
+
function getSectionsFromMarkdownMetadata(text) {
|
|
111408
|
+
const lines = text.split("\n");
|
|
111409
|
+
let sections = [];
|
|
111410
|
+
let currentSection = null;
|
|
111411
|
+
let currentKey = null;
|
|
111412
|
+
let currentValue = null;
|
|
111413
|
+
let listStack = [];
|
|
111414
|
+
let currentIndent = 0;
|
|
111415
|
+
for (const line of lines) {
|
|
111416
|
+
if (!line.trim()) {
|
|
111417
|
+
if (currentValue) {
|
|
111418
|
+
currentValue += "\n";
|
|
111419
|
+
}
|
|
111420
|
+
continue;
|
|
111421
|
+
}
|
|
111422
|
+
if (line.startsWith("#")) {
|
|
111423
|
+
const headerMatch = /^(#+)\s*(.+)$/.exec(line);
|
|
111424
|
+
if (headerMatch) {
|
|
111425
|
+
pushSection();
|
|
111426
|
+
currentSection = {
|
|
111427
|
+
headerKind: headerMatch[1].length === 1 ? "single" :
|
|
111428
|
+
(headerMatch[1].length === 2 ? "double" : "triple"),
|
|
111429
|
+
header: headerMatch[2],
|
|
111430
|
+
attributes: {}
|
|
111431
|
+
};
|
|
111432
|
+
currentKey = null;
|
|
111433
|
+
currentValue = null;
|
|
111434
|
+
currentIndent = 0;
|
|
111435
|
+
continue;
|
|
111436
|
+
}
|
|
111437
|
+
}
|
|
111438
|
+
if (currentSection) {
|
|
111439
|
+
const indent = countIndent(line);
|
|
111440
|
+
const trimmedLine = line.trim();
|
|
111441
|
+
const keyMatch = /^[*-]\s+(?:([^:]+):)?(.*)$/.exec(trimmedLine);
|
|
111442
|
+
if (!keyMatch)
|
|
111443
|
+
continue;
|
|
111444
|
+
// We ignore indent changes of 1 space to make the list authoring a little
|
|
111445
|
+
// bit friendlier. Likewise, indents can be any length greater than 1 space
|
|
111446
|
+
if (Math.abs(indent - currentIndent) > 1 && currentKey) {
|
|
111447
|
+
if (indent > currentIndent) {
|
|
111448
|
+
const newList = {
|
|
111449
|
+
key: currentKey,
|
|
111450
|
+
items: []
|
|
111451
|
+
};
|
|
111452
|
+
if (listStack.length) {
|
|
111453
|
+
listStack[listStack.length - 1].items.push(newList);
|
|
111454
|
+
}
|
|
111455
|
+
else {
|
|
111456
|
+
if (!currentSection.listAttributes)
|
|
111457
|
+
currentSection.listAttributes = {};
|
|
111458
|
+
currentSection.listAttributes[currentKey] = newList;
|
|
111459
|
+
}
|
|
111460
|
+
currentKey = null;
|
|
111461
|
+
listStack.push(newList);
|
|
111462
|
+
}
|
|
111463
|
+
else {
|
|
111464
|
+
const prev = listStack.pop();
|
|
111465
|
+
if (currentKey && currentValue) {
|
|
111466
|
+
prev === null || prev === void 0 ? void 0 : prev.items.push((currentKey + ":" + currentValue).trim());
|
|
111467
|
+
currentValue = null;
|
|
111468
|
+
}
|
|
111469
|
+
}
|
|
111470
|
+
currentIndent = indent;
|
|
111471
|
+
}
|
|
111472
|
+
if (keyMatch) {
|
|
111473
|
+
if (keyMatch[1]) {
|
|
111474
|
+
if (currentKey && currentValue) {
|
|
111475
|
+
if (listStack.length) {
|
|
111476
|
+
listStack[listStack.length - 1].items.push((currentKey + ":" + currentValue).trim());
|
|
111477
|
+
}
|
|
111478
|
+
else {
|
|
111479
|
+
currentSection.attributes[currentKey] = currentValue.trim();
|
|
111480
|
+
}
|
|
111481
|
+
}
|
|
111482
|
+
currentKey = keyMatch[1].toLowerCase();
|
|
111483
|
+
currentValue = keyMatch[2];
|
|
111484
|
+
}
|
|
111485
|
+
else if (currentKey) {
|
|
111486
|
+
currentValue += keyMatch[2];
|
|
111487
|
+
}
|
|
111488
|
+
}
|
|
111489
|
+
}
|
|
111490
|
+
}
|
|
111491
|
+
pushSection();
|
|
111492
|
+
return sections;
|
|
111493
|
+
function pushSection() {
|
|
111494
|
+
if (currentSection) {
|
|
111495
|
+
if (currentKey && currentValue) {
|
|
111496
|
+
if (listStack.length) {
|
|
111497
|
+
listStack[listStack.length - 1].items.push((currentKey + ":" + currentValue).trim());
|
|
111498
|
+
}
|
|
111499
|
+
else {
|
|
111500
|
+
currentSection.attributes[currentKey] = currentValue.trim();
|
|
111501
|
+
}
|
|
111502
|
+
}
|
|
111503
|
+
sections.push(currentSection);
|
|
111504
|
+
}
|
|
111505
|
+
listStack = [];
|
|
111506
|
+
}
|
|
111507
|
+
}
|
|
111508
|
+
pxt.getSectionsFromMarkdownMetadata = getSectionsFromMarkdownMetadata;
|
|
111509
|
+
// Handles tabs and spaces, but a mix of them might end up with strange results. Not much
|
|
111510
|
+
// we can do about that so just treat 1 tab as 4 spaces
|
|
111511
|
+
function countIndent(line) {
|
|
111512
|
+
let indent = 0;
|
|
111513
|
+
for (let i = 0; i < line.length; i++) {
|
|
111514
|
+
if (line.charAt(i) === " ")
|
|
111515
|
+
indent++;
|
|
111516
|
+
else if (line.charAt(i) === "\t")
|
|
111517
|
+
indent += 4;
|
|
111518
|
+
else
|
|
111519
|
+
return indent;
|
|
111520
|
+
}
|
|
111521
|
+
return 0;
|
|
111522
|
+
}
|
|
111523
|
+
})(pxt || (pxt = {}));
|
|
111524
|
+
var pxt;
|
|
111404
111525
|
(function (pxt) {
|
|
111405
111526
|
var multiplayer;
|
|
111406
111527
|
(function (multiplayer) {
|
|
@@ -118220,6 +118341,7 @@ var pxt;
|
|
|
118220
118341
|
}
|
|
118221
118342
|
const assetFiles = parseAssetJson(assetJson);
|
|
118222
118343
|
const globalBlockConfig = parseTutorialBlockConfig("global", tutorialmd);
|
|
118344
|
+
const globalValidationConfig = parseTutorialValidationConfig("global", tutorialmd);
|
|
118223
118345
|
// strip hidden snippets
|
|
118224
118346
|
steps.forEach(step => {
|
|
118225
118347
|
step.contentMd = stripHiddenSnippets(step.contentMd);
|
|
@@ -118240,7 +118362,8 @@ var pxt;
|
|
|
118240
118362
|
assetFiles,
|
|
118241
118363
|
customTs,
|
|
118242
118364
|
tutorialValidationRules,
|
|
118243
|
-
globalBlockConfig
|
|
118365
|
+
globalBlockConfig,
|
|
118366
|
+
globalValidationConfig
|
|
118244
118367
|
};
|
|
118245
118368
|
}
|
|
118246
118369
|
tutorial.parseTutorial = parseTutorial;
|
|
@@ -118422,6 +118545,7 @@ ${code}
|
|
|
118422
118545
|
step = step.trim();
|
|
118423
118546
|
let { header, hint, requiredBlocks } = parseTutorialHint(step, metadata && metadata.explicitHints, metadata.tutorialCodeValidation);
|
|
118424
118547
|
const blockConfig = parseTutorialBlockConfig("local", step);
|
|
118548
|
+
const validationConfig = parseTutorialValidationConfig("local", step);
|
|
118425
118549
|
// if title is not hidden ("{TITLE HERE}"), strip flags
|
|
118426
118550
|
const title = !flags.match(/^\{.*\}$/)
|
|
118427
118551
|
? flags.replace(/@(fullscreen|unplugged|showdialog|showhint|tutorialCompleted|resetDiff)/gi, "").trim()
|
|
@@ -118430,7 +118554,8 @@ ${code}
|
|
|
118430
118554
|
title,
|
|
118431
118555
|
contentMd: step,
|
|
118432
118556
|
headerContentMd: header,
|
|
118433
|
-
localBlockConfig: blockConfig
|
|
118557
|
+
localBlockConfig: blockConfig,
|
|
118558
|
+
localValidationConfig: validationConfig
|
|
118434
118559
|
};
|
|
118435
118560
|
if (/@(fullscreen|unplugged|showdialog|showhint)/i.test(flags))
|
|
118436
118561
|
info.showHint = true;
|
|
@@ -118497,6 +118622,28 @@ ${code}
|
|
|
118497
118622
|
});
|
|
118498
118623
|
return blockConfig;
|
|
118499
118624
|
}
|
|
118625
|
+
function parseTutorialValidationConfig(scope, content) {
|
|
118626
|
+
let markdown;
|
|
118627
|
+
const regex = new RegExp(`\`\`\`\\s*validation\\.${scope}\\s*\\n([\\s\\S]*?)\\n\`\`\``, "gmi");
|
|
118628
|
+
content.replace(regex, (m0, m1) => {
|
|
118629
|
+
markdown = m1;
|
|
118630
|
+
return "";
|
|
118631
|
+
});
|
|
118632
|
+
if (!markdown || markdown == "") {
|
|
118633
|
+
return null;
|
|
118634
|
+
}
|
|
118635
|
+
const validationSections = pxt.getSectionsFromMarkdownMetadata(markdown);
|
|
118636
|
+
const sectionedMetadata = validationSections.map((v) => {
|
|
118637
|
+
return {
|
|
118638
|
+
validatorType: v.header,
|
|
118639
|
+
properties: v.attributes,
|
|
118640
|
+
};
|
|
118641
|
+
});
|
|
118642
|
+
return {
|
|
118643
|
+
validatorsMetadata: sectionedMetadata,
|
|
118644
|
+
validators: undefined
|
|
118645
|
+
};
|
|
118646
|
+
}
|
|
118500
118647
|
function categorizingValidationRules(listOfRules, title) {
|
|
118501
118648
|
const ruleNames = Object.keys(listOfRules);
|
|
118502
118649
|
for (let i = 0; i < ruleNames.length; i++) {
|
|
@@ -118512,7 +118659,7 @@ ${code}
|
|
|
118512
118659
|
function stripHiddenSnippets(str) {
|
|
118513
118660
|
if (!str)
|
|
118514
118661
|
return str;
|
|
118515
|
-
const hiddenSnippetRegex = /```(filterblocks|package|ghost|config|template|jres|assetjson|customts|blockconfig\.local|blockconfig\.global)\s*\n([\s\S]*?)\n```/gmi;
|
|
118662
|
+
const hiddenSnippetRegex = /```(filterblocks|package|ghost|config|template|jres|assetjson|customts|blockconfig\.local|blockconfig\.global|validation\.local|validation\.global)\s*\n([\s\S]*?)\n```/gmi;
|
|
118516
118663
|
return str.replace(hiddenSnippetRegex, '').trim();
|
|
118517
118664
|
}
|
|
118518
118665
|
/*
|
|
@@ -118595,7 +118742,8 @@ ${code}
|
|
|
118595
118742
|
assetFiles: tutorialInfo.assetFiles,
|
|
118596
118743
|
customTs: tutorialInfo.customTs,
|
|
118597
118744
|
tutorialValidationRules: tutorialInfo.tutorialValidationRules,
|
|
118598
|
-
globalBlockConfig: tutorialInfo.globalBlockConfig
|
|
118745
|
+
globalBlockConfig: tutorialInfo.globalBlockConfig,
|
|
118746
|
+
globalValidationConfig: tutorialInfo.globalValidationConfig
|
|
118599
118747
|
};
|
|
118600
118748
|
return { options: tutorialOptions, editor: tutorialInfo.editor };
|
|
118601
118749
|
}
|
|
@@ -118609,13 +118757,13 @@ ${code}
|
|
|
118609
118757
|
if (id && cachedInfo[id]) {
|
|
118610
118758
|
const info = cachedInfo[id];
|
|
118611
118759
|
if (info.usedBlocks && info.hash)
|
|
118612
|
-
db.setWithHashAsync(id, info.snippetBlocks, info.hash);
|
|
118760
|
+
db.setWithHashAsync(id, info.snippetBlocks, info.hash, info.highlightBlocks);
|
|
118613
118761
|
}
|
|
118614
118762
|
else {
|
|
118615
118763
|
for (let key of Object.keys(cachedInfo)) {
|
|
118616
118764
|
const info = cachedInfo[key];
|
|
118617
118765
|
if (info.usedBlocks && info.hash)
|
|
118618
|
-
db.setWithHashAsync(key, info.snippetBlocks, info.hash);
|
|
118766
|
+
db.setWithHashAsync(key, info.snippetBlocks, info.hash, info.highlightBlocks);
|
|
118619
118767
|
}
|
|
118620
118768
|
}
|
|
118621
118769
|
}).catch((err) => { });
|
|
@@ -164633,7 +164781,7 @@ function internalCacheUsedBlocksAsync() {
|
|
|
164633
164781
|
const decompiled = pxtc.decompileSnippets(pxtc.getTSProgram(opts), opts, false);
|
|
164634
164782
|
if ((decompiled === null || decompiled === void 0 ? void 0 : decompiled.length) > 0) {
|
|
164635
164783
|
// scrape block IDs matching <block type="block_id">
|
|
164636
|
-
let builtInfo = builtTututorialInfo[hash] || { usedBlocks: {}, snippetBlocks: {} };
|
|
164784
|
+
let builtInfo = builtTututorialInfo[hash] || { usedBlocks: {}, snippetBlocks: {}, highlightBlocks: {} };
|
|
164637
164785
|
const blockIdRegex = /<\s*block(?:[^>]*)? type="([^ ]*)"/ig;
|
|
164638
164786
|
for (let i = 0; i < decompiled.length; i++) {
|
|
164639
164787
|
const blocksXml = decompiled[i];
|
|
@@ -164643,6 +164791,7 @@ function internalCacheUsedBlocksAsync() {
|
|
|
164643
164791
|
builtInfo.snippetBlocks[snippetHash] = {};
|
|
164644
164792
|
builtInfo.snippetBlocks[snippetHash][m1] = 1;
|
|
164645
164793
|
builtInfo.usedBlocks[m1] = 1;
|
|
164794
|
+
//TODO: Fill builtInfo.HighlightedBlocks
|
|
164646
164795
|
return m0;
|
|
164647
164796
|
});
|
|
164648
164797
|
}
|
package/built/pxtlib.d.ts
CHANGED
|
@@ -826,10 +826,11 @@ declare namespace pxt.BrowserUtils {
|
|
|
826
826
|
hash: string;
|
|
827
827
|
blocks: Map<number>;
|
|
828
828
|
snippets: Map<Map<number>>;
|
|
829
|
+
highlightBlocks: Map<Map<number>>;
|
|
829
830
|
}
|
|
830
831
|
export interface ITutorialInfoDb {
|
|
831
832
|
getAsync(filename: string, code: string[], branch?: string): Promise<TutorialInfoIndexedDbEntry>;
|
|
832
|
-
setAsync(filename: string, snippets: Map<Map<number>>, code: string[], branch?: string): Promise<void>;
|
|
833
|
+
setAsync(filename: string, snippets: Map<Map<number>>, code: string[], highlights: Map<Map<number>>, branch?: string): Promise<void>;
|
|
833
834
|
clearAsync(): Promise<void>;
|
|
834
835
|
}
|
|
835
836
|
class TutorialInfoIndexedDb implements ITutorialInfoDb {
|
|
@@ -840,8 +841,8 @@ declare namespace pxt.BrowserUtils {
|
|
|
840
841
|
static createAsync(): Promise<TutorialInfoIndexedDb>;
|
|
841
842
|
private constructor();
|
|
842
843
|
getAsync(filename: string, code: string[], branch?: string): Promise<TutorialInfoIndexedDbEntry>;
|
|
843
|
-
setAsync(filename: string, snippets: Map<Map<number>>, code: string[], branch?: string): Promise<void>;
|
|
844
|
-
setWithHashAsync(filename: string, snippets: Map<Map<number>>, hash: string, branch?: string): Promise<void>;
|
|
844
|
+
setAsync(filename: string, snippets: Map<Map<number>>, code: string[], highlights: Map<Map<number>>, branch?: string): Promise<void>;
|
|
845
|
+
setWithHashAsync(filename: string, snippets: Map<Map<number>>, hash: string, highlights: Map<Map<number>>, branch?: string): Promise<void>;
|
|
845
846
|
clearAsync(): Promise<void>;
|
|
846
847
|
}
|
|
847
848
|
export function tutorialInfoDbAsync(): Promise<TutorialInfoIndexedDb>;
|
|
@@ -1779,6 +1780,19 @@ declare namespace pxt.storage.shared {
|
|
|
1779
1780
|
function setAsync(container: string, key: string, val: any): Promise<void>;
|
|
1780
1781
|
function delAsync(container: string, key: string): Promise<void>;
|
|
1781
1782
|
}
|
|
1783
|
+
declare namespace pxt {
|
|
1784
|
+
interface MarkdownSection {
|
|
1785
|
+
headerKind: "single" | "double" | "triple";
|
|
1786
|
+
header: string;
|
|
1787
|
+
attributes: pxt.Map<string>;
|
|
1788
|
+
listAttributes?: pxt.Map<MarkdownList>;
|
|
1789
|
+
}
|
|
1790
|
+
interface MarkdownList {
|
|
1791
|
+
key: string;
|
|
1792
|
+
items: (string | MarkdownList)[];
|
|
1793
|
+
}
|
|
1794
|
+
function getSectionsFromMarkdownMetadata(text: string): MarkdownSection[];
|
|
1795
|
+
}
|
|
1782
1796
|
declare namespace pxt.multiplayer {
|
|
1783
1797
|
const SHORT_LINKS: {
|
|
1784
1798
|
PROD: string;
|
package/built/pxtlib.js
CHANGED
|
@@ -2678,7 +2678,8 @@ var ts;
|
|
|
2678
2678
|
res.tutorialInfo[key] = {
|
|
2679
2679
|
hash: built.hash,
|
|
2680
2680
|
usedBlocks: Object.assign({}, built.usedBlocks),
|
|
2681
|
-
snippetBlocks: Object.assign({}, built.snippetBlocks)
|
|
2681
|
+
snippetBlocks: Object.assign({}, built.snippetBlocks),
|
|
2682
|
+
highlightBlocks: Object.assign({}, built.highlightBlocks)
|
|
2682
2683
|
};
|
|
2683
2684
|
}
|
|
2684
2685
|
}
|
|
@@ -5529,7 +5530,7 @@ var pxt;
|
|
|
5529
5530
|
}
|
|
5530
5531
|
static createAsync() {
|
|
5531
5532
|
function openAsync() {
|
|
5532
|
-
const idbWrapper = new pxt.BrowserUtils.IDBWrapper(TutorialInfoIndexedDb.dbName(),
|
|
5533
|
+
const idbWrapper = new pxt.BrowserUtils.IDBWrapper(TutorialInfoIndexedDb.dbName(), 3, (ev, r) => {
|
|
5533
5534
|
const db = r.result;
|
|
5534
5535
|
db.createObjectStore(TutorialInfoIndexedDb.TABLE, { keyPath: TutorialInfoIndexedDb.KEYPATH });
|
|
5535
5536
|
}, () => {
|
|
@@ -5559,13 +5560,13 @@ var pxt;
|
|
|
5559
5560
|
return undefined;
|
|
5560
5561
|
});
|
|
5561
5562
|
}
|
|
5562
|
-
setAsync(filename, snippets, code, branch) {
|
|
5563
|
+
setAsync(filename, snippets, code, highlights, branch) {
|
|
5563
5564
|
pxt.perf.measureStart("tutorial info db setAsync");
|
|
5564
5565
|
const key = getTutorialInfoKey(filename, branch);
|
|
5565
5566
|
const hash = getTutorialCodeHash(code);
|
|
5566
|
-
return this.setWithHashAsync(filename, snippets, hash);
|
|
5567
|
+
return this.setWithHashAsync(filename, snippets, hash, highlights);
|
|
5567
5568
|
}
|
|
5568
|
-
setWithHashAsync(filename, snippets, hash, branch) {
|
|
5569
|
+
setWithHashAsync(filename, snippets, hash, highlights, branch) {
|
|
5569
5570
|
pxt.perf.measureStart("tutorial info db setAsync");
|
|
5570
5571
|
const key = getTutorialInfoKey(filename, branch);
|
|
5571
5572
|
const blocks = {};
|
|
@@ -5579,7 +5580,8 @@ var pxt;
|
|
|
5579
5580
|
time: pxt.Util.now(),
|
|
5580
5581
|
hash,
|
|
5581
5582
|
snippets,
|
|
5582
|
-
blocks
|
|
5583
|
+
blocks,
|
|
5584
|
+
highlightBlocks: highlights,
|
|
5583
5585
|
};
|
|
5584
5586
|
return this.db.setAsync(TutorialInfoIndexedDb.TABLE, entry)
|
|
5585
5587
|
.then(() => {
|
|
@@ -13715,6 +13717,125 @@ var pxt;
|
|
|
13715
13717
|
})(storage = pxt.storage || (pxt.storage = {}));
|
|
13716
13718
|
})(pxt || (pxt = {}));
|
|
13717
13719
|
var pxt;
|
|
13720
|
+
(function (pxt) {
|
|
13721
|
+
function getSectionsFromMarkdownMetadata(text) {
|
|
13722
|
+
const lines = text.split("\n");
|
|
13723
|
+
let sections = [];
|
|
13724
|
+
let currentSection = null;
|
|
13725
|
+
let currentKey = null;
|
|
13726
|
+
let currentValue = null;
|
|
13727
|
+
let listStack = [];
|
|
13728
|
+
let currentIndent = 0;
|
|
13729
|
+
for (const line of lines) {
|
|
13730
|
+
if (!line.trim()) {
|
|
13731
|
+
if (currentValue) {
|
|
13732
|
+
currentValue += "\n";
|
|
13733
|
+
}
|
|
13734
|
+
continue;
|
|
13735
|
+
}
|
|
13736
|
+
if (line.startsWith("#")) {
|
|
13737
|
+
const headerMatch = /^(#+)\s*(.+)$/.exec(line);
|
|
13738
|
+
if (headerMatch) {
|
|
13739
|
+
pushSection();
|
|
13740
|
+
currentSection = {
|
|
13741
|
+
headerKind: headerMatch[1].length === 1 ? "single" :
|
|
13742
|
+
(headerMatch[1].length === 2 ? "double" : "triple"),
|
|
13743
|
+
header: headerMatch[2],
|
|
13744
|
+
attributes: {}
|
|
13745
|
+
};
|
|
13746
|
+
currentKey = null;
|
|
13747
|
+
currentValue = null;
|
|
13748
|
+
currentIndent = 0;
|
|
13749
|
+
continue;
|
|
13750
|
+
}
|
|
13751
|
+
}
|
|
13752
|
+
if (currentSection) {
|
|
13753
|
+
const indent = countIndent(line);
|
|
13754
|
+
const trimmedLine = line.trim();
|
|
13755
|
+
const keyMatch = /^[*-]\s+(?:([^:]+):)?(.*)$/.exec(trimmedLine);
|
|
13756
|
+
if (!keyMatch)
|
|
13757
|
+
continue;
|
|
13758
|
+
// We ignore indent changes of 1 space to make the list authoring a little
|
|
13759
|
+
// bit friendlier. Likewise, indents can be any length greater than 1 space
|
|
13760
|
+
if (Math.abs(indent - currentIndent) > 1 && currentKey) {
|
|
13761
|
+
if (indent > currentIndent) {
|
|
13762
|
+
const newList = {
|
|
13763
|
+
key: currentKey,
|
|
13764
|
+
items: []
|
|
13765
|
+
};
|
|
13766
|
+
if (listStack.length) {
|
|
13767
|
+
listStack[listStack.length - 1].items.push(newList);
|
|
13768
|
+
}
|
|
13769
|
+
else {
|
|
13770
|
+
if (!currentSection.listAttributes)
|
|
13771
|
+
currentSection.listAttributes = {};
|
|
13772
|
+
currentSection.listAttributes[currentKey] = newList;
|
|
13773
|
+
}
|
|
13774
|
+
currentKey = null;
|
|
13775
|
+
listStack.push(newList);
|
|
13776
|
+
}
|
|
13777
|
+
else {
|
|
13778
|
+
const prev = listStack.pop();
|
|
13779
|
+
if (currentKey && currentValue) {
|
|
13780
|
+
prev === null || prev === void 0 ? void 0 : prev.items.push((currentKey + ":" + currentValue).trim());
|
|
13781
|
+
currentValue = null;
|
|
13782
|
+
}
|
|
13783
|
+
}
|
|
13784
|
+
currentIndent = indent;
|
|
13785
|
+
}
|
|
13786
|
+
if (keyMatch) {
|
|
13787
|
+
if (keyMatch[1]) {
|
|
13788
|
+
if (currentKey && currentValue) {
|
|
13789
|
+
if (listStack.length) {
|
|
13790
|
+
listStack[listStack.length - 1].items.push((currentKey + ":" + currentValue).trim());
|
|
13791
|
+
}
|
|
13792
|
+
else {
|
|
13793
|
+
currentSection.attributes[currentKey] = currentValue.trim();
|
|
13794
|
+
}
|
|
13795
|
+
}
|
|
13796
|
+
currentKey = keyMatch[1].toLowerCase();
|
|
13797
|
+
currentValue = keyMatch[2];
|
|
13798
|
+
}
|
|
13799
|
+
else if (currentKey) {
|
|
13800
|
+
currentValue += keyMatch[2];
|
|
13801
|
+
}
|
|
13802
|
+
}
|
|
13803
|
+
}
|
|
13804
|
+
}
|
|
13805
|
+
pushSection();
|
|
13806
|
+
return sections;
|
|
13807
|
+
function pushSection() {
|
|
13808
|
+
if (currentSection) {
|
|
13809
|
+
if (currentKey && currentValue) {
|
|
13810
|
+
if (listStack.length) {
|
|
13811
|
+
listStack[listStack.length - 1].items.push((currentKey + ":" + currentValue).trim());
|
|
13812
|
+
}
|
|
13813
|
+
else {
|
|
13814
|
+
currentSection.attributes[currentKey] = currentValue.trim();
|
|
13815
|
+
}
|
|
13816
|
+
}
|
|
13817
|
+
sections.push(currentSection);
|
|
13818
|
+
}
|
|
13819
|
+
listStack = [];
|
|
13820
|
+
}
|
|
13821
|
+
}
|
|
13822
|
+
pxt.getSectionsFromMarkdownMetadata = getSectionsFromMarkdownMetadata;
|
|
13823
|
+
// Handles tabs and spaces, but a mix of them might end up with strange results. Not much
|
|
13824
|
+
// we can do about that so just treat 1 tab as 4 spaces
|
|
13825
|
+
function countIndent(line) {
|
|
13826
|
+
let indent = 0;
|
|
13827
|
+
for (let i = 0; i < line.length; i++) {
|
|
13828
|
+
if (line.charAt(i) === " ")
|
|
13829
|
+
indent++;
|
|
13830
|
+
else if (line.charAt(i) === "\t")
|
|
13831
|
+
indent += 4;
|
|
13832
|
+
else
|
|
13833
|
+
return indent;
|
|
13834
|
+
}
|
|
13835
|
+
return 0;
|
|
13836
|
+
}
|
|
13837
|
+
})(pxt || (pxt = {}));
|
|
13838
|
+
var pxt;
|
|
13718
13839
|
(function (pxt) {
|
|
13719
13840
|
var multiplayer;
|
|
13720
13841
|
(function (multiplayer) {
|
|
@@ -20534,6 +20655,7 @@ var pxt;
|
|
|
20534
20655
|
}
|
|
20535
20656
|
const assetFiles = parseAssetJson(assetJson);
|
|
20536
20657
|
const globalBlockConfig = parseTutorialBlockConfig("global", tutorialmd);
|
|
20658
|
+
const globalValidationConfig = parseTutorialValidationConfig("global", tutorialmd);
|
|
20537
20659
|
// strip hidden snippets
|
|
20538
20660
|
steps.forEach(step => {
|
|
20539
20661
|
step.contentMd = stripHiddenSnippets(step.contentMd);
|
|
@@ -20554,7 +20676,8 @@ var pxt;
|
|
|
20554
20676
|
assetFiles,
|
|
20555
20677
|
customTs,
|
|
20556
20678
|
tutorialValidationRules,
|
|
20557
|
-
globalBlockConfig
|
|
20679
|
+
globalBlockConfig,
|
|
20680
|
+
globalValidationConfig
|
|
20558
20681
|
};
|
|
20559
20682
|
}
|
|
20560
20683
|
tutorial.parseTutorial = parseTutorial;
|
|
@@ -20736,6 +20859,7 @@ ${code}
|
|
|
20736
20859
|
step = step.trim();
|
|
20737
20860
|
let { header, hint, requiredBlocks } = parseTutorialHint(step, metadata && metadata.explicitHints, metadata.tutorialCodeValidation);
|
|
20738
20861
|
const blockConfig = parseTutorialBlockConfig("local", step);
|
|
20862
|
+
const validationConfig = parseTutorialValidationConfig("local", step);
|
|
20739
20863
|
// if title is not hidden ("{TITLE HERE}"), strip flags
|
|
20740
20864
|
const title = !flags.match(/^\{.*\}$/)
|
|
20741
20865
|
? flags.replace(/@(fullscreen|unplugged|showdialog|showhint|tutorialCompleted|resetDiff)/gi, "").trim()
|
|
@@ -20744,7 +20868,8 @@ ${code}
|
|
|
20744
20868
|
title,
|
|
20745
20869
|
contentMd: step,
|
|
20746
20870
|
headerContentMd: header,
|
|
20747
|
-
localBlockConfig: blockConfig
|
|
20871
|
+
localBlockConfig: blockConfig,
|
|
20872
|
+
localValidationConfig: validationConfig
|
|
20748
20873
|
};
|
|
20749
20874
|
if (/@(fullscreen|unplugged|showdialog|showhint)/i.test(flags))
|
|
20750
20875
|
info.showHint = true;
|
|
@@ -20811,6 +20936,28 @@ ${code}
|
|
|
20811
20936
|
});
|
|
20812
20937
|
return blockConfig;
|
|
20813
20938
|
}
|
|
20939
|
+
function parseTutorialValidationConfig(scope, content) {
|
|
20940
|
+
let markdown;
|
|
20941
|
+
const regex = new RegExp(`\`\`\`\\s*validation\\.${scope}\\s*\\n([\\s\\S]*?)\\n\`\`\``, "gmi");
|
|
20942
|
+
content.replace(regex, (m0, m1) => {
|
|
20943
|
+
markdown = m1;
|
|
20944
|
+
return "";
|
|
20945
|
+
});
|
|
20946
|
+
if (!markdown || markdown == "") {
|
|
20947
|
+
return null;
|
|
20948
|
+
}
|
|
20949
|
+
const validationSections = pxt.getSectionsFromMarkdownMetadata(markdown);
|
|
20950
|
+
const sectionedMetadata = validationSections.map((v) => {
|
|
20951
|
+
return {
|
|
20952
|
+
validatorType: v.header,
|
|
20953
|
+
properties: v.attributes,
|
|
20954
|
+
};
|
|
20955
|
+
});
|
|
20956
|
+
return {
|
|
20957
|
+
validatorsMetadata: sectionedMetadata,
|
|
20958
|
+
validators: undefined
|
|
20959
|
+
};
|
|
20960
|
+
}
|
|
20814
20961
|
function categorizingValidationRules(listOfRules, title) {
|
|
20815
20962
|
const ruleNames = Object.keys(listOfRules);
|
|
20816
20963
|
for (let i = 0; i < ruleNames.length; i++) {
|
|
@@ -20826,7 +20973,7 @@ ${code}
|
|
|
20826
20973
|
function stripHiddenSnippets(str) {
|
|
20827
20974
|
if (!str)
|
|
20828
20975
|
return str;
|
|
20829
|
-
const hiddenSnippetRegex = /```(filterblocks|package|ghost|config|template|jres|assetjson|customts|blockconfig\.local|blockconfig\.global)\s*\n([\s\S]*?)\n```/gmi;
|
|
20976
|
+
const hiddenSnippetRegex = /```(filterblocks|package|ghost|config|template|jres|assetjson|customts|blockconfig\.local|blockconfig\.global|validation\.local|validation\.global)\s*\n([\s\S]*?)\n```/gmi;
|
|
20830
20977
|
return str.replace(hiddenSnippetRegex, '').trim();
|
|
20831
20978
|
}
|
|
20832
20979
|
/*
|
|
@@ -20909,7 +21056,8 @@ ${code}
|
|
|
20909
21056
|
assetFiles: tutorialInfo.assetFiles,
|
|
20910
21057
|
customTs: tutorialInfo.customTs,
|
|
20911
21058
|
tutorialValidationRules: tutorialInfo.tutorialValidationRules,
|
|
20912
|
-
globalBlockConfig: tutorialInfo.globalBlockConfig
|
|
21059
|
+
globalBlockConfig: tutorialInfo.globalBlockConfig,
|
|
21060
|
+
globalValidationConfig: tutorialInfo.globalValidationConfig
|
|
20913
21061
|
};
|
|
20914
21062
|
return { options: tutorialOptions, editor: tutorialInfo.editor };
|
|
20915
21063
|
}
|
|
@@ -20923,13 +21071,13 @@ ${code}
|
|
|
20923
21071
|
if (id && cachedInfo[id]) {
|
|
20924
21072
|
const info = cachedInfo[id];
|
|
20925
21073
|
if (info.usedBlocks && info.hash)
|
|
20926
|
-
db.setWithHashAsync(id, info.snippetBlocks, info.hash);
|
|
21074
|
+
db.setWithHashAsync(id, info.snippetBlocks, info.hash, info.highlightBlocks);
|
|
20927
21075
|
}
|
|
20928
21076
|
else {
|
|
20929
21077
|
for (let key of Object.keys(cachedInfo)) {
|
|
20930
21078
|
const info = cachedInfo[key];
|
|
20931
21079
|
if (info.usedBlocks && info.hash)
|
|
20932
|
-
db.setWithHashAsync(key, info.snippetBlocks, info.hash);
|
|
21080
|
+
db.setWithHashAsync(key, info.snippetBlocks, info.hash, info.highlightBlocks);
|
|
20933
21081
|
}
|
|
20934
21082
|
}
|
|
20935
21083
|
}).catch((err) => { });
|