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 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(), 2, (ev, r) => {
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(), 2, (ev, r) => {
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) => { });