pxt-core 8.5.13 → 8.5.15
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 +159 -13
- package/built/pxtlib.d.ts +17 -3
- package/built/pxtlib.js +157 -12
- package/built/target.js +1 -1
- package/built/web/main.js +1 -1
- package/built/web/multiplayer/js/{main.1a1e5af6.js → main.816fb70a.js} +2 -2
- 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 +34 -0
- package/package.json +1 -1
- package/theme/tutorial-sidebar.less +85 -0
- package/webapp/public/multiplayer.html +1 -1
- 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,25 @@ ${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 { validatorsMetadata: sectionedMetadata };
|
|
118643
|
+
}
|
|
118500
118644
|
function categorizingValidationRules(listOfRules, title) {
|
|
118501
118645
|
const ruleNames = Object.keys(listOfRules);
|
|
118502
118646
|
for (let i = 0; i < ruleNames.length; i++) {
|
|
@@ -118512,7 +118656,7 @@ ${code}
|
|
|
118512
118656
|
function stripHiddenSnippets(str) {
|
|
118513
118657
|
if (!str)
|
|
118514
118658
|
return str;
|
|
118515
|
-
const hiddenSnippetRegex = /```(filterblocks|package|ghost|config|template|jres|assetjson|customts|blockconfig\.local|blockconfig\.global)\s*\n([\s\S]*?)\n```/gmi;
|
|
118659
|
+
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
118660
|
return str.replace(hiddenSnippetRegex, '').trim();
|
|
118517
118661
|
}
|
|
118518
118662
|
/*
|
|
@@ -118595,7 +118739,8 @@ ${code}
|
|
|
118595
118739
|
assetFiles: tutorialInfo.assetFiles,
|
|
118596
118740
|
customTs: tutorialInfo.customTs,
|
|
118597
118741
|
tutorialValidationRules: tutorialInfo.tutorialValidationRules,
|
|
118598
|
-
globalBlockConfig: tutorialInfo.globalBlockConfig
|
|
118742
|
+
globalBlockConfig: tutorialInfo.globalBlockConfig,
|
|
118743
|
+
globalValidationConfig: tutorialInfo.globalValidationConfig
|
|
118599
118744
|
};
|
|
118600
118745
|
return { options: tutorialOptions, editor: tutorialInfo.editor };
|
|
118601
118746
|
}
|
|
@@ -118609,13 +118754,13 @@ ${code}
|
|
|
118609
118754
|
if (id && cachedInfo[id]) {
|
|
118610
118755
|
const info = cachedInfo[id];
|
|
118611
118756
|
if (info.usedBlocks && info.hash)
|
|
118612
|
-
db.setWithHashAsync(id, info.snippetBlocks, info.hash);
|
|
118757
|
+
db.setWithHashAsync(id, info.snippetBlocks, info.hash, info.highlightBlocks);
|
|
118613
118758
|
}
|
|
118614
118759
|
else {
|
|
118615
118760
|
for (let key of Object.keys(cachedInfo)) {
|
|
118616
118761
|
const info = cachedInfo[key];
|
|
118617
118762
|
if (info.usedBlocks && info.hash)
|
|
118618
|
-
db.setWithHashAsync(key, info.snippetBlocks, info.hash);
|
|
118763
|
+
db.setWithHashAsync(key, info.snippetBlocks, info.hash, info.highlightBlocks);
|
|
118619
118764
|
}
|
|
118620
118765
|
}
|
|
118621
118766
|
}).catch((err) => { });
|
|
@@ -164633,7 +164778,7 @@ function internalCacheUsedBlocksAsync() {
|
|
|
164633
164778
|
const decompiled = pxtc.decompileSnippets(pxtc.getTSProgram(opts), opts, false);
|
|
164634
164779
|
if ((decompiled === null || decompiled === void 0 ? void 0 : decompiled.length) > 0) {
|
|
164635
164780
|
// scrape block IDs matching <block type="block_id">
|
|
164636
|
-
let builtInfo = builtTututorialInfo[hash] || { usedBlocks: {}, snippetBlocks: {} };
|
|
164781
|
+
let builtInfo = builtTututorialInfo[hash] || { usedBlocks: {}, snippetBlocks: {}, highlightBlocks: {} };
|
|
164637
164782
|
const blockIdRegex = /<\s*block(?:[^>]*)? type="([^ ]*)"/ig;
|
|
164638
164783
|
for (let i = 0; i < decompiled.length; i++) {
|
|
164639
164784
|
const blocksXml = decompiled[i];
|
|
@@ -164643,6 +164788,7 @@ function internalCacheUsedBlocksAsync() {
|
|
|
164643
164788
|
builtInfo.snippetBlocks[snippetHash] = {};
|
|
164644
164789
|
builtInfo.snippetBlocks[snippetHash][m1] = 1;
|
|
164645
164790
|
builtInfo.usedBlocks[m1] = 1;
|
|
164791
|
+
//TODO: Fill builtInfo.HighlightedBlocks
|
|
164646
164792
|
return m0;
|
|
164647
164793
|
});
|
|
164648
164794
|
}
|
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,25 @@ ${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 { validatorsMetadata: sectionedMetadata };
|
|
20957
|
+
}
|
|
20814
20958
|
function categorizingValidationRules(listOfRules, title) {
|
|
20815
20959
|
const ruleNames = Object.keys(listOfRules);
|
|
20816
20960
|
for (let i = 0; i < ruleNames.length; i++) {
|
|
@@ -20826,7 +20970,7 @@ ${code}
|
|
|
20826
20970
|
function stripHiddenSnippets(str) {
|
|
20827
20971
|
if (!str)
|
|
20828
20972
|
return str;
|
|
20829
|
-
const hiddenSnippetRegex = /```(filterblocks|package|ghost|config|template|jres|assetjson|customts|blockconfig\.local|blockconfig\.global)\s*\n([\s\S]*?)\n```/gmi;
|
|
20973
|
+
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
20974
|
return str.replace(hiddenSnippetRegex, '').trim();
|
|
20831
20975
|
}
|
|
20832
20976
|
/*
|
|
@@ -20909,7 +21053,8 @@ ${code}
|
|
|
20909
21053
|
assetFiles: tutorialInfo.assetFiles,
|
|
20910
21054
|
customTs: tutorialInfo.customTs,
|
|
20911
21055
|
tutorialValidationRules: tutorialInfo.tutorialValidationRules,
|
|
20912
|
-
globalBlockConfig: tutorialInfo.globalBlockConfig
|
|
21056
|
+
globalBlockConfig: tutorialInfo.globalBlockConfig,
|
|
21057
|
+
globalValidationConfig: tutorialInfo.globalValidationConfig
|
|
20913
21058
|
};
|
|
20914
21059
|
return { options: tutorialOptions, editor: tutorialInfo.editor };
|
|
20915
21060
|
}
|
|
@@ -20923,13 +21068,13 @@ ${code}
|
|
|
20923
21068
|
if (id && cachedInfo[id]) {
|
|
20924
21069
|
const info = cachedInfo[id];
|
|
20925
21070
|
if (info.usedBlocks && info.hash)
|
|
20926
|
-
db.setWithHashAsync(id, info.snippetBlocks, info.hash);
|
|
21071
|
+
db.setWithHashAsync(id, info.snippetBlocks, info.hash, info.highlightBlocks);
|
|
20927
21072
|
}
|
|
20928
21073
|
else {
|
|
20929
21074
|
for (let key of Object.keys(cachedInfo)) {
|
|
20930
21075
|
const info = cachedInfo[key];
|
|
20931
21076
|
if (info.usedBlocks && info.hash)
|
|
20932
|
-
db.setWithHashAsync(key, info.snippetBlocks, info.hash);
|
|
21077
|
+
db.setWithHashAsync(key, info.snippetBlocks, info.hash, info.highlightBlocks);
|
|
20933
21078
|
}
|
|
20934
21079
|
}
|
|
20935
21080
|
}).catch((err) => { });
|