scratchblocks-plus 1.0.0

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.
Files changed (119) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +193 -0
  3. package/browser.es.js +8 -0
  4. package/browser.js +8 -0
  5. package/build/scratchblocks-plus.min.es.js +12 -0
  6. package/build/scratchblocks-plus.min.es.js.map +1 -0
  7. package/build/scratchblocks-plus.min.js +12 -0
  8. package/build/scratchblocks-plus.min.js.map +1 -0
  9. package/build/translations-all-es.js +11 -0
  10. package/build/translations-all-es.js.map +1 -0
  11. package/build/translations-all.js +11 -0
  12. package/build/translations-all.js.map +1 -0
  13. package/build/translations-es.js +11 -0
  14. package/build/translations-es.js.map +1 -0
  15. package/build/translations.js +11 -0
  16. package/build/translations.js.map +1 -0
  17. package/index.d.ts +297 -0
  18. package/index.js +229 -0
  19. package/locales/ab.json +1630 -0
  20. package/locales/af.json +1630 -0
  21. package/locales/all.d.ts +108 -0
  22. package/locales/all.js +161 -0
  23. package/locales/am.json +1925 -0
  24. package/locales/an.json +1630 -0
  25. package/locales/ar.json +1924 -0
  26. package/locales/ast.json +1630 -0
  27. package/locales/az.json +1925 -0
  28. package/locales/be.json +1630 -0
  29. package/locales/bg.json +1924 -0
  30. package/locales/bn.json +1630 -0
  31. package/locales/ca.json +1930 -0
  32. package/locales/ckb.json +1630 -0
  33. package/locales/cs.json +1930 -0
  34. package/locales/cy.json +1929 -0
  35. package/locales/da.json +1924 -0
  36. package/locales/de.json +1929 -0
  37. package/locales/el.json +1931 -0
  38. package/locales/eo.json +1630 -0
  39. package/locales/es-419.json +1924 -0
  40. package/locales/es.json +1929 -0
  41. package/locales/et.json +1924 -0
  42. package/locales/eu.json +1924 -0
  43. package/locales/fa.json +1929 -0
  44. package/locales/fi.json +1924 -0
  45. package/locales/fil.json +1631 -0
  46. package/locales/forums.js +37 -0
  47. package/locales/fr.json +1929 -0
  48. package/locales/fy.json +1630 -0
  49. package/locales/ga.json +1924 -0
  50. package/locales/gd.json +1929 -0
  51. package/locales/gl.json +1924 -0
  52. package/locales/ha.json +1630 -0
  53. package/locales/he.json +1929 -0
  54. package/locales/hi.json +1635 -0
  55. package/locales/hr.json +1929 -0
  56. package/locales/ht.json +1630 -0
  57. package/locales/hu.json +1930 -0
  58. package/locales/hy.json +1630 -0
  59. package/locales/id.json +1929 -0
  60. package/locales/is.json +1924 -0
  61. package/locales/it.json +1929 -0
  62. package/locales/ja-Hira.json +1637 -0
  63. package/locales/ja.json +1931 -0
  64. package/locales/ka.json +1630 -0
  65. package/locales/kk.json +1632 -0
  66. package/locales/km.json +1630 -0
  67. package/locales/ko.json +1924 -0
  68. package/locales/ku.json +1632 -0
  69. package/locales/lt.json +1924 -0
  70. package/locales/lv.json +1924 -0
  71. package/locales/mi.json +1924 -0
  72. package/locales/mn.json +1631 -0
  73. package/locales/nb.json +1929 -0
  74. package/locales/nl.json +1929 -0
  75. package/locales/nn.json +1630 -0
  76. package/locales/nso.json +1630 -0
  77. package/locales/oc.json +1630 -0
  78. package/locales/or.json +1631 -0
  79. package/locales/pl.json +1929 -0
  80. package/locales/pt-br.json +1924 -0
  81. package/locales/pt.json +1929 -0
  82. package/locales/qu.json +1630 -0
  83. package/locales/rap.json +1632 -0
  84. package/locales/ro.json +1929 -0
  85. package/locales/ru.json +1929 -0
  86. package/locales/sk.json +1924 -0
  87. package/locales/sl.json +1929 -0
  88. package/locales/sr.json +1924 -0
  89. package/locales/sv.json +1924 -0
  90. package/locales/sw.json +1630 -0
  91. package/locales/th.json +1924 -0
  92. package/locales/tn.json +1630 -0
  93. package/locales/tr.json +1932 -0
  94. package/locales/uk.json +1924 -0
  95. package/locales/uz.json +1631 -0
  96. package/locales/vi.json +1925 -0
  97. package/locales/xh.json +1630 -0
  98. package/locales/zh-cn.json +1930 -0
  99. package/locales/zh-tw.json +1930 -0
  100. package/locales/zu.json +1918 -0
  101. package/package.json +81 -0
  102. package/scratch2/blocks.js +1000 -0
  103. package/scratch2/draw.js +452 -0
  104. package/scratch2/filter.js +78 -0
  105. package/scratch2/index.js +12 -0
  106. package/scratch2/style.css.js +148 -0
  107. package/scratch2/style.js +214 -0
  108. package/scratch3/blocks.js +1134 -0
  109. package/scratch3/draw.js +334 -0
  110. package/scratch3/index.js +12 -0
  111. package/scratch3/style.css.js +280 -0
  112. package/scratch3/style.js +877 -0
  113. package/syntax/blocks.js +921 -0
  114. package/syntax/commands.js +1755 -0
  115. package/syntax/dropdowns.js +688 -0
  116. package/syntax/extensions.js +34 -0
  117. package/syntax/index.js +17 -0
  118. package/syntax/model.js +566 -0
  119. package/syntax/syntax.js +1091 -0
@@ -0,0 +1,921 @@
1
+ import { extensions, aliasExtensions } from "./extensions.js"
2
+
3
+ // List of classes we're allowed to override.
4
+
5
+ const overrideCategories = [
6
+ "motion",
7
+ "looks",
8
+ "sound",
9
+ "variables",
10
+ "list",
11
+ "events",
12
+ "control",
13
+ "sensing",
14
+ "operators",
15
+ "custom",
16
+ "custom-arg",
17
+ "extension",
18
+ "grey",
19
+ "obsolete",
20
+ ...Object.keys(extensions),
21
+ ...Object.keys(aliasExtensions),
22
+ ]
23
+
24
+ const overrideShapes = [
25
+ "hat",
26
+ "cap",
27
+ "stack",
28
+ "boolean",
29
+ "reporter",
30
+ "ring",
31
+ "cat",
32
+ ]
33
+
34
+ // languages that should be displayed right to left
35
+ export const rtlLanguages = ["ar", "ckb", "fa", "he"]
36
+
37
+ // List of commands taken from Scratch
38
+ import scratchCommands from "./commands.js"
39
+ import {
40
+ getDropdowns as getScratchDropdowns,
41
+ getMakeyMakeySequenceDropdowns,
42
+ } from "./dropdowns.js"
43
+
44
+ const inputNumberPat = /%([0-9]+)/
45
+ export const inputPat = /(%[a-zA-Z0-9](?:\.[a-zA-Z0-9]+)?)/
46
+ const inputPatGlobal = new RegExp(inputPat.source, "g")
47
+ export const iconPat = /(@[a-zA-Z]+)/
48
+ const splitPat = new RegExp(`${inputPat.source}|${iconPat.source}| +`, "g")
49
+
50
+ export const hexColorPat = /^#(?:[0-9a-fA-F]{3}){1,2}?$/
51
+
52
+ export function parseInputNumber(part) {
53
+ const m = inputNumberPat.exec(part)
54
+ return m ? +m[1] : 0
55
+ }
56
+
57
+ // used for procDefs
58
+ export function parseSpec(spec) {
59
+ const parts = spec.split(splitPat).filter(x => x)
60
+ const inputs = parts.filter(p => inputPat.test(p))
61
+ return { spec: spec, parts: parts, inputs: inputs, hash: hashSpec(spec) }
62
+ }
63
+
64
+ export function hashSpec(spec) {
65
+ return minifyHash(spec.replace(inputPatGlobal, " _ "))
66
+ }
67
+
68
+ export function minifyHash(hash) {
69
+ return hash
70
+ .replace(/_/g, " _ ")
71
+ .replace(/ +/g, " ")
72
+ .replace(/[,%?:]/g, "")
73
+ .replace(/ß/g, "ss")
74
+ .replace(/ä/g, "a")
75
+ .replace(/ö/g, "o")
76
+ .replace(/ü/g, "u")
77
+ .replace(". . .", "...")
78
+ .replace(/^…$/, "...")
79
+ .trim()
80
+ .toLowerCase()
81
+ }
82
+
83
+ export const blocksById = {}
84
+ const allBlocks = scratchCommands.map(def => {
85
+ if (!def.id) {
86
+ if (!def.selector) {
87
+ throw new Error(`Missing ID: ${def.spec}`)
88
+ }
89
+ def.id = `sb2:${def.selector}`
90
+ }
91
+ if (!def.spec) {
92
+ throw new Error(`Missing spec: ${def.id}`)
93
+ }
94
+
95
+ const info = {
96
+ id: def.id, // Used for Scratch 3 translations
97
+ spec: def.spec, // Used for Scratch 2 translations
98
+ parts: def.spec.split(splitPat).filter(x => x),
99
+ selector: def.selector || `sb3:${def.id}`, // Used for JSON marshalling
100
+ inputs: def.inputs == null ? [] : def.inputs,
101
+ shape: def.shape,
102
+ category: def.category,
103
+ hasLoopArrow: !!def.hasLoopArrow,
104
+ }
105
+ if (blocksById[info.id]) {
106
+ throw new Error(`Duplicate ID: ${info.id}`)
107
+ }
108
+ blocksById[info.id] = info
109
+ return info
110
+ })
111
+
112
+ export const unicodeIcons = {
113
+ "@greenFlag": "⚑",
114
+ "@turnRight": "↻",
115
+ "@turnLeft": "↺",
116
+ "@addInput": "▸",
117
+ "@delInput": "◂",
118
+ }
119
+
120
+ export const allLanguages = {}
121
+ function loadLanguage(code, language) {
122
+ const blocksByHash = (language.blocksByHash = {})
123
+
124
+ Object.keys(language.commands).forEach(blockId => {
125
+ const nativeSpec = language.commands[blockId]
126
+ const block = blocksById[blockId]
127
+
128
+ const nativeHash = hashSpec(nativeSpec)
129
+ if (!blocksByHash[nativeHash]) {
130
+ blocksByHash[nativeHash] = []
131
+ }
132
+ blocksByHash[nativeHash].push(block)
133
+
134
+ // fallback image replacement, for languages without aliases
135
+ const m = iconPat.exec(block.spec)
136
+ if (m) {
137
+ const image = m[0]
138
+ const hash = nativeHash.replace(hashSpec(image), unicodeIcons[image])
139
+ if (!blocksByHash[hash]) {
140
+ blocksByHash[hash] = []
141
+ }
142
+ blocksByHash[hash].push(block)
143
+ }
144
+ })
145
+
146
+ language.nativeAliases = {}
147
+ Object.keys(language.aliases).forEach(alias => {
148
+ const blockId = language.aliases[alias]
149
+ const block = blocksById[blockId]
150
+ if (block === undefined) {
151
+ throw new Error(`Invalid alias '${blockId}'`)
152
+ }
153
+ const aliasHash = hashSpec(alias)
154
+ if (!blocksByHash[aliasHash]) {
155
+ blocksByHash[aliasHash] = []
156
+ }
157
+ blocksByHash[aliasHash].push(block)
158
+
159
+ if (!language.nativeAliases[blockId]) {
160
+ language.nativeAliases[blockId] = []
161
+ }
162
+ language.nativeAliases[blockId].push(alias)
163
+ })
164
+
165
+ // Some English blocks were renamed between Scratch 2 and Scratch 3. Wire them
166
+ // into language.blocksByHash
167
+ Object.keys(language.renamedBlocks || {}).forEach(alt => {
168
+ const id = language.renamedBlocks[alt]
169
+ if (!blocksById[id]) {
170
+ throw new Error(`Unknown ID: ${id}`)
171
+ }
172
+ const block = blocksById[id]
173
+ const hash = hashSpec(alt)
174
+ if (!english.blocksByHash[hash]) {
175
+ english.blocksByHash[hash] = []
176
+ }
177
+ english.blocksByHash[hash].push(block)
178
+ })
179
+
180
+ language.nativeDropdowns = {}
181
+ Object.keys(language.dropdowns).forEach(name => {
182
+ const nativeName = language.dropdowns[name].value
183
+ const parents = language.dropdowns[name].parents
184
+ if (!language.nativeDropdowns[nativeName]) {
185
+ language.nativeDropdowns[nativeName] = []
186
+ }
187
+ language.nativeDropdowns[nativeName].push({ id: name, parents: parents })
188
+ })
189
+
190
+ language.code = code
191
+ allLanguages[code] = language
192
+ }
193
+ export function loadLanguages(languages) {
194
+ Object.keys(languages).forEach(code => loadLanguage(code, languages[code]))
195
+ }
196
+
197
+ export const english = {
198
+ aliases: {
199
+ "turn ccw %1 degrees": "MOTION_TURNLEFT",
200
+ "turn left %1 degrees": "MOTION_TURNLEFT",
201
+ "turn cw %1 degrees": "MOTION_TURNRIGHT",
202
+ "turn right %1 degrees": "MOTION_TURNRIGHT",
203
+ "when flag clicked": "EVENT_WHENFLAGCLICKED",
204
+ "when gf clicked": "EVENT_WHENFLAGCLICKED",
205
+ "when green flag clicked": "EVENT_WHENFLAGCLICKED",
206
+ },
207
+
208
+ renamedBlocks: {
209
+ "say %1 for %2 secs": "LOOKS_SAYFORSECS",
210
+ "think %1 for %2 secs": "LOOKS_THINKFORSECS",
211
+ "play sound %1": "SOUND_PLAY",
212
+ "wait %1 secs": "CONTROL_WAIT",
213
+ clear: "pen.clear",
214
+ },
215
+
216
+ definePrefix: ["define"],
217
+ defineSuffix: [],
218
+
219
+ // For ignoring the lt sign in the "when distance < _" block
220
+ ignorelt: ["when distance"],
221
+
222
+ // Valid arguments to "of" dropdown, for resolving ambiguous situations
223
+ math: [
224
+ "abs",
225
+ "floor",
226
+ "ceiling",
227
+ "sqrt",
228
+ "sin",
229
+ "cos",
230
+ "tan",
231
+ "asin",
232
+ "acos",
233
+ "atan",
234
+ "ln",
235
+ "log",
236
+ "e ^",
237
+ "10 ^",
238
+ ],
239
+
240
+ // Language name is needed for the English locale as well
241
+ name: "English",
242
+
243
+ // Valid arguments to "go to" face sensing dropdown, for resolving ambiguous situations
244
+ faceParts: [
245
+ "nose",
246
+ "mouth",
247
+ "left eye",
248
+ "right eye",
249
+ "between eyes",
250
+ "left ear",
251
+ "right ear",
252
+ "top of head",
253
+ ],
254
+
255
+ // Valid arguments to "sound effect" dropdown, for resolving ambiguous situations
256
+ soundEffects: ["pitch", "pan left/right"],
257
+
258
+ // Valid arguments to "microbit when" dropdown
259
+ microbitWhen: ["moved", "shaken", "jumped"],
260
+
261
+ // For detecting the "stop" cap / stack block
262
+ osis: ["other scripts in sprite", "other scripts in stage"],
263
+
264
+ dropdowns: {},
265
+
266
+ commands: {},
267
+ }
268
+ allBlocks.forEach(info => {
269
+ english.commands[info.id] = info.spec
270
+ })
271
+ getScratchDropdowns().forEach(info => {
272
+ english.dropdowns[info.id] = {
273
+ value: info.value,
274
+ parents: info.parents || [],
275
+ }
276
+ })
277
+ english.dropdowns = {
278
+ ...english.dropdowns,
279
+ ...getMakeyMakeySequenceDropdowns(english.dropdowns),
280
+ "translate.language.am": {
281
+ value: "Amharic",
282
+ parents: ["translate.translateBlock"],
283
+ },
284
+ "translate.language.ar": {
285
+ value: "Arabic",
286
+ parents: ["translate.translateBlock"],
287
+ },
288
+ "translate.language.az": {
289
+ value: "Azerbaijani",
290
+ parents: ["translate.translateBlock"],
291
+ },
292
+ "translate.language.eu": {
293
+ value: "Basque",
294
+ parents: ["translate.translateBlock"],
295
+ },
296
+ "translate.language.bg": {
297
+ value: "Bulgarian",
298
+ parents: ["translate.translateBlock"],
299
+ },
300
+ "translate.language.ca": {
301
+ value: "Catalan",
302
+ parents: ["translate.translateBlock"],
303
+ },
304
+ "translate.language.zh-cn": {
305
+ value: "Chinese (Simplified)",
306
+ parents: ["translate.translateBlock"],
307
+ },
308
+ "translate.language.zh-tw": {
309
+ value: "Chinese (Traditional)",
310
+ parents: ["translate.translateBlock"],
311
+ },
312
+ "translate.language.hr": {
313
+ value: "Croatian",
314
+ parents: ["translate.translateBlock"],
315
+ },
316
+ "translate.language.cs": {
317
+ value: "Czech",
318
+ parents: ["translate.translateBlock"],
319
+ },
320
+ "translate.language.da": {
321
+ value: "Danish",
322
+ parents: ["translate.translateBlock"],
323
+ },
324
+ "translate.language.nl": {
325
+ value: "Dutch",
326
+ parents: ["translate.translateBlock"],
327
+ },
328
+ "translate.language.en": {
329
+ value: "English",
330
+ parents: ["translate.translateBlock"],
331
+ },
332
+ "translate.language.et": {
333
+ value: "Estonian",
334
+ parents: ["translate.translateBlock"],
335
+ },
336
+ "translate.language.fi": {
337
+ value: "Finnish",
338
+ parents: ["translate.translateBlock"],
339
+ },
340
+ "translate.language.fr": {
341
+ value: "French",
342
+ parents: ["translate.translateBlock"],
343
+ },
344
+ "translate.language.gl": {
345
+ value: "Galician",
346
+ parents: ["translate.translateBlock"],
347
+ },
348
+ "translate.language.de": {
349
+ value: "German",
350
+ parents: ["translate.translateBlock"],
351
+ },
352
+ "translate.language.el": {
353
+ value: "Greek",
354
+ parents: ["translate.translateBlock"],
355
+ },
356
+ "translate.language.he": {
357
+ value: "Hebrew",
358
+ parents: ["translate.translateBlock"],
359
+ },
360
+ "translate.language.hu": {
361
+ value: "Hungarian",
362
+ parents: ["translate.translateBlock"],
363
+ },
364
+ "translate.language.is": {
365
+ value: "Icelandic",
366
+ parents: ["translate.translateBlock"],
367
+ },
368
+ "translate.language.id": {
369
+ value: "Indonesian",
370
+ parents: ["translate.translateBlock"],
371
+ },
372
+ "translate.language.ga": {
373
+ value: "Irish",
374
+ parents: ["translate.translateBlock"],
375
+ },
376
+ "translate.language.it": {
377
+ value: "Italian",
378
+ parents: ["translate.translateBlock"],
379
+ },
380
+ "translate.language.ja": {
381
+ value: "Japanese",
382
+ parents: ["translate.translateBlock"],
383
+ },
384
+ "translate.language.ko": {
385
+ value: "Korean",
386
+ parents: ["translate.translateBlock"],
387
+ },
388
+ "translate.language.lv": {
389
+ value: "Latvian",
390
+ parents: ["translate.translateBlock"],
391
+ },
392
+ "translate.language.lt": {
393
+ value: "Lithuanian",
394
+ parents: ["translate.translateBlock"],
395
+ },
396
+ "translate.language.mi": {
397
+ value: "Maori",
398
+ parents: ["translate.translateBlock"],
399
+ },
400
+ "translate.language.nb": {
401
+ value: "Norwegian",
402
+ parents: ["translate.translateBlock"],
403
+ },
404
+ "translate.language.fa": {
405
+ value: "Persian",
406
+ parents: ["translate.translateBlock"],
407
+ },
408
+ "translate.language.pl": {
409
+ value: "Polish",
410
+ parents: ["translate.translateBlock"],
411
+ },
412
+ "translate.language.pt": {
413
+ value: "Portuguese",
414
+ parents: ["translate.translateBlock"],
415
+ },
416
+ "translate.language.ro": {
417
+ value: "Romanian",
418
+ parents: ["translate.translateBlock"],
419
+ },
420
+ "translate.language.ru": {
421
+ value: "Russian",
422
+ parents: ["translate.translateBlock"],
423
+ },
424
+ "translate.language.gd": {
425
+ value: "Scots Gaelic",
426
+ parents: ["translate.translateBlock"],
427
+ },
428
+ "translate.language.sr": {
429
+ value: "Serbian",
430
+ parents: ["translate.translateBlock"],
431
+ },
432
+ "translate.language.sk": {
433
+ value: "Slovak",
434
+ parents: ["translate.translateBlock"],
435
+ },
436
+ "translate.language.sl": {
437
+ value: "Slovenian",
438
+ parents: ["translate.translateBlock"],
439
+ },
440
+ "translate.language.es": {
441
+ value: "Spanish",
442
+ parents: ["translate.translateBlock"],
443
+ },
444
+ "translate.language.sv": {
445
+ value: "Swedish",
446
+ parents: ["translate.translateBlock"],
447
+ },
448
+ "translate.language.th": {
449
+ value: "Thai",
450
+ parents: ["translate.translateBlock"],
451
+ },
452
+ "translate.language.tr": {
453
+ value: "Turkish",
454
+ parents: ["translate.translateBlock"],
455
+ },
456
+ "translate.language.uk": {
457
+ value: "Ukrainian",
458
+ parents: ["translate.translateBlock"],
459
+ },
460
+ "translate.language.vi": {
461
+ value: "Vietnamese",
462
+ parents: ["translate.translateBlock"],
463
+ },
464
+ "translate.language.cy": {
465
+ value: "Welsh",
466
+ parents: ["translate.translateBlock"],
467
+ },
468
+ "translate.language.zu": {
469
+ value: "Zulu",
470
+ parents: ["translate.translateBlock"],
471
+ },
472
+ "text2speech.language.ar": {
473
+ value: "Arabic",
474
+ parents: ["text2speech.setLanguageBlock"],
475
+ },
476
+ "text2speech.language.zh-cn": {
477
+ value: "Chinese (Mandarin)",
478
+ parents: ["text2speech.setLanguageBlock"],
479
+ },
480
+ "text2speech.language.da": {
481
+ value: "Danish",
482
+ parents: ["text2speech.setLanguageBlock"],
483
+ },
484
+ "text2speech.language.nl": {
485
+ value: "Dutch",
486
+ parents: ["text2speech.setLanguageBlock"],
487
+ },
488
+ "text2speech.language.en": {
489
+ value: "English",
490
+ parents: ["text2speech.setLanguageBlock"],
491
+ },
492
+ "text2speech.language.fr": {
493
+ value: "French",
494
+ parents: ["text2speech.setLanguageBlock"],
495
+ },
496
+ "text2speech.language.de": {
497
+ value: "German",
498
+ parents: ["text2speech.setLanguageBlock"],
499
+ },
500
+ "text2speech.language.hi": {
501
+ value: "Hindi",
502
+ parents: ["text2speech.setLanguageBlock"],
503
+ },
504
+ "text2speech.language.is": {
505
+ value: "Icelandic",
506
+ parents: ["text2speech.setLanguageBlock"],
507
+ },
508
+ "text2speech.language.it": {
509
+ value: "Italian",
510
+ parents: ["text2speech.setLanguageBlock"],
511
+ },
512
+ "text2speech.language.ja": {
513
+ value: "Japanese",
514
+ parents: ["text2speech.setLanguageBlock"],
515
+ },
516
+ "text2speech.language.ko": {
517
+ value: "Korean",
518
+ parents: ["text2speech.setLanguageBlock"],
519
+ },
520
+ "text2speech.language.nb": {
521
+ value: "Norwegian",
522
+ parents: ["text2speech.setLanguageBlock"],
523
+ },
524
+ "text2speech.language.pl": {
525
+ value: "Polish",
526
+ parents: ["text2speech.setLanguageBlock"],
527
+ },
528
+ "text2speech.language.pt-br": {
529
+ value: "Portuguese (Brazilian)",
530
+ parents: ["text2speech.setLanguageBlock"],
531
+ },
532
+ "text2speech.language.pt": {
533
+ value: "Portuguese",
534
+ parents: ["text2speech.setLanguageBlock"],
535
+ },
536
+ "text2speech.language.ro": {
537
+ value: "Romanian",
538
+ parents: ["text2speech.setLanguageBlock"],
539
+ },
540
+ "text2speech.language.ru": {
541
+ value: "Russian",
542
+ parents: ["text2speech.setLanguageBlock"],
543
+ },
544
+ "text2speech.language.es": {
545
+ value: "Spanish",
546
+ parents: ["text2speech.setLanguageBlock"],
547
+ },
548
+ "text2speech.language.es-419": {
549
+ value: "Spanish (Latin American)",
550
+ parents: ["text2speech.setLanguageBlock"],
551
+ },
552
+ "text2speech.language.sv": {
553
+ value: "Swedish",
554
+ parents: ["text2speech.setLanguageBlock"],
555
+ },
556
+ "text2speech.language.tr": {
557
+ value: "Turkish",
558
+ parents: ["text2speech.setLanguageBlock"],
559
+ },
560
+ "text2speech.language.cy": {
561
+ value: "Welsh",
562
+ parents: ["text2speech.setLanguageBlock"],
563
+ },
564
+ }
565
+ loadLanguages({ en: english })
566
+
567
+ /*****************************************************************************/
568
+
569
+ function registerCheck(id, func) {
570
+ if (!blocksById[id]) {
571
+ throw new Error(`Unknown ID: ${id}`)
572
+ }
573
+ blocksById[id].accepts = func
574
+ }
575
+
576
+ function specialCase(id, func) {
577
+ if (!blocksById[id]) {
578
+ throw new Error(`Unknown ID: ${id}`)
579
+ }
580
+ blocksById[id].specialCase = func
581
+ }
582
+
583
+ function disambig(id1, id2, test) {
584
+ registerCheck(id1, (_, children, lang) => {
585
+ return test(children, lang)
586
+ })
587
+ registerCheck(id2, (_, children, lang) => {
588
+ return !test(children, lang)
589
+ })
590
+ }
591
+
592
+ disambig("OPERATORS_MATHOP", "SENSING_OF", (children, lang) => {
593
+ // Operators if math function, otherwise sensing "attribute of" block
594
+ const first = children[0]
595
+ if (!first.isInput) {
596
+ return
597
+ }
598
+ const name = first.value
599
+ return lang.math.includes(name)
600
+ })
601
+
602
+ disambig("SOUND_CHANGEEFFECTBY", "LOOKS_CHANGEEFFECTBY", (children, lang) => {
603
+ // Sound if sound effect, otherwise default to graphic effect
604
+ for (const child of children) {
605
+ if (child.shape === "dropdown") {
606
+ const name = child.value
607
+ for (const effect of lang.soundEffects) {
608
+ if (minifyHash(effect) === minifyHash(name)) {
609
+ return true
610
+ }
611
+ }
612
+ }
613
+ }
614
+ return false
615
+ })
616
+
617
+ disambig("SOUND_SETEFFECTO", "LOOKS_SETEFFECTTO", (children, lang) => {
618
+ // Sound if sound effect, otherwise default to graphic effect
619
+ for (const child of children) {
620
+ if (child.shape === "dropdown") {
621
+ const name = child.value
622
+ for (const effect of lang.soundEffects) {
623
+ if (minifyHash(effect) === minifyHash(name)) {
624
+ return true
625
+ }
626
+ }
627
+ }
628
+ }
629
+ return false
630
+ })
631
+
632
+ disambig("DATA_LENGTHOFLIST", "OPERATORS_LENGTH", (children, _lang) => {
633
+ // List block if dropdown, otherwise operators
634
+ const last = children[children.length - 1]
635
+ if (!last.isInput) {
636
+ return
637
+ }
638
+ return last.shape === "dropdown"
639
+ })
640
+
641
+ disambig("DATA_LISTCONTAINSITEM", "OPERATORS_CONTAINS", (children, _lang) => {
642
+ // List block if dropdown, otherwise operators
643
+ const first = children[0]
644
+ if (!first.isInput) {
645
+ return
646
+ }
647
+ return first.shape === "dropdown"
648
+ })
649
+
650
+ disambig("pen.setColor", "pen.setHue", (children, _lang) => {
651
+ // Color block if color input, otherwise numeric
652
+ const last = children[children.length - 1]
653
+ // If variable, assume color input, since the RGBA hack is common.
654
+ // TODO fix Scratch :P
655
+ return (last.isInput && last.isColor) || last.isBlock
656
+ })
657
+
658
+ disambig("faceSensing.goToPart", "MOTION_GOTO", (children, lang) => {
659
+ // Face sensing if face part, otherwise default to motion block
660
+ for (const child of children) {
661
+ if (child.shape === "dropdown") {
662
+ const name = child.value
663
+ for (const effect of lang.faceParts) {
664
+ if (minifyHash(effect) === minifyHash(name)) {
665
+ return true
666
+ }
667
+ }
668
+ }
669
+ }
670
+ return false
671
+ })
672
+
673
+ disambig("microbit.whenGesture", "gdxfor.whenGesture", (children, lang) => {
674
+ for (const child of children) {
675
+ if (child.shape === "dropdown") {
676
+ const name = child.value
677
+ // Yes, "when shaken" gdxfor block exists. But microbit is more common.
678
+ for (const effect of lang.microbitWhen) {
679
+ if (minifyHash(effect) === minifyHash(name)) {
680
+ return true
681
+ }
682
+ }
683
+ }
684
+ }
685
+ return false
686
+ })
687
+
688
+ // This block does not need disambiguation in English;
689
+ // however, many other languages do require that.
690
+ disambig("ev3.buttonPressed", "microbit.isButtonPressed", (children, _lang) => {
691
+ for (const child of children) {
692
+ if (child.shape === "dropdown") {
693
+ // EV3 "button pressed" block uses numeric identifier
694
+ // and does not support "any".
695
+ switch (minifyHash(child.value)) {
696
+ case "1":
697
+ case "2":
698
+ case "3":
699
+ case "4":
700
+ return true
701
+ default:
702
+ }
703
+ }
704
+ }
705
+ return false
706
+ })
707
+
708
+ specialCase("CONTROL_STOP", (_, children, lang) => {
709
+ // Cap block unless argument is "other scripts in sprite"
710
+ const last = children[children.length - 1]
711
+ if (!last.isInput) {
712
+ return
713
+ }
714
+ const value = last.value
715
+ if (lang.osis.includes(value)) {
716
+ return { ...blocksById.CONTROL_STOP, shape: "stack" }
717
+ }
718
+ })
719
+
720
+ function narrowByNativeDropdowns(collisions, children, languages) {
721
+ const dropdownValues = []
722
+ for (const child of children) {
723
+ if (!child) {
724
+ continue
725
+ }
726
+ const shape = child.shape || ""
727
+ if (shape.endsWith("dropdown") || shape === "dropdown") {
728
+ if (child.value != null) {
729
+ dropdownValues.push(child.value)
730
+ }
731
+ }
732
+ }
733
+ if (!dropdownValues.length) {
734
+ return collisions
735
+ }
736
+
737
+ // Preprocess: build a map value -> concatenated list of dropdown entry strings
738
+ const valueEntriesMap = new Map()
739
+ for (const lang of languages) {
740
+ for (const val of dropdownValues) {
741
+ if (
742
+ lang.nativeDropdowns &&
743
+ Object.prototype.hasOwnProperty.call(lang.nativeDropdowns, val)
744
+ ) {
745
+ const arr = lang.nativeDropdowns[val]
746
+ if (arr && arr.length) {
747
+ if (!valueEntriesMap.has(val)) {
748
+ valueEntriesMap.set(val, [])
749
+ }
750
+ // arr: [{ id, parents }] ; merge (keep duplicates harmless)
751
+ valueEntriesMap.get(val).push(...arr)
752
+ }
753
+ }
754
+ }
755
+ }
756
+ if (!valueEntriesMap.size) {
757
+ return collisions
758
+ }
759
+
760
+ const filtered = collisions.filter(block => {
761
+ const candidates = [
762
+ block.id,
763
+ block.selector && block.selector.replace(/^sb3:/, ""),
764
+ block.selector,
765
+ ].filter(Boolean)
766
+ for (const entries of valueEntriesMap.values()) {
767
+ for (const entry of entries) {
768
+ if (!entry) {
769
+ continue
770
+ }
771
+ // entry can be { id, parents } per loadLanguage(); parents may be undefined
772
+ const parents = Array.isArray(entry.parents) ? entry.parents : []
773
+ // If any parent matches the block id/selector, keep it
774
+ if (parents.some(p => candidates.includes(p))) {
775
+ return true
776
+ }
777
+ // Fallback: some (older) data could have stored id as parent indicator
778
+ if (entry.id && candidates.includes(entry.id)) {
779
+ return true
780
+ }
781
+ }
782
+ }
783
+ return false
784
+ })
785
+ return filtered.length ? filtered : collisions
786
+ }
787
+
788
+ export function lookupHash(hash, info, children, languages, overrides) {
789
+ for (const lang of languages) {
790
+ if (Object.prototype.hasOwnProperty.call(lang.blocksByHash, hash)) {
791
+ let collisions = lang.blocksByHash[hash]
792
+
793
+ if (overrides && overrides.length) {
794
+ // 1) Highest priority: If a value in overrides matches a category in collisions uniquely, return it directly
795
+ const collisionCategories = new Set(
796
+ collisions.map(b => b.category).filter(Boolean),
797
+ )
798
+ for (const o of overrides) {
799
+ if (!o) {
800
+ continue
801
+ }
802
+ // Only process if the override appears in the current collision category set
803
+ if (collisionCategories.has(o)) {
804
+ const matching = collisions.filter(b => b.category === o)
805
+ if (matching.length === 1) {
806
+ const block = matching[0]
807
+ // Keep shape filtering consistent with subsequent logic
808
+ if (
809
+ (info.shape === "reporter" &&
810
+ block.shape !== "reporter" &&
811
+ block.shape !== "ring") ||
812
+ (info.shape === "boolean" && block.shape !== "boolean")
813
+ ) {
814
+ // If the shape is incompatible, do not break, continue to try other overrides
815
+ } else {
816
+ return { type: block, lang: lang }
817
+ }
818
+ } else if (matching.length > 1) {
819
+ // Multiple same categories, narrow down the collision range, continue further refinement
820
+ collisions = matching
821
+ break
822
+ }
823
+ }
824
+ }
825
+
826
+ // 2) Secondary: Use the original mechanism, identify the first override in the allowed override category list
827
+ const categoryOverride = overrides.find(o =>
828
+ overrideCategories.includes(o),
829
+ )
830
+ if (categoryOverride) {
831
+ const filtered = collisions.filter(
832
+ b => b.category === categoryOverride,
833
+ )
834
+ if (filtered.length) {
835
+ // If only one remains, return it directly; otherwise, continue normal disambiguation
836
+ if (filtered.length === 1) {
837
+ return { type: filtered[0], lang: lang }
838
+ }
839
+ collisions = filtered
840
+ }
841
+ }
842
+ }
843
+
844
+ // Try to narrow down the collision set using nativeDropdowns
845
+ if (collisions.length > 1) {
846
+ collisions = narrowByNativeDropdowns(collisions, children, languages)
847
+ }
848
+
849
+ for (let block of collisions) {
850
+ if (
851
+ info.shape === "reporter" &&
852
+ block.shape !== "reporter" &&
853
+ block.shape !== "ring"
854
+ ) {
855
+ continue
856
+ }
857
+ if (info.shape === "boolean" && block.shape !== "boolean") {
858
+ continue
859
+ }
860
+ if (collisions.length > 1) {
861
+ // Only check in case of collision;
862
+ // perform "disambiguation"
863
+ if (block.accepts && !block.accepts(info, children, lang)) {
864
+ continue
865
+ }
866
+ }
867
+ if (block.specialCase) {
868
+ block = block.specialCase(info, children, lang) || block
869
+ }
870
+ return { type: block, lang: lang }
871
+ }
872
+ }
873
+ }
874
+ }
875
+
876
+ export function lookupDropdown(name, parentType, languages) {
877
+ for (const lang of languages) {
878
+ if (Object.prototype.hasOwnProperty.call(lang.nativeDropdowns, name)) {
879
+ const objs = lang.nativeDropdowns[name]
880
+ for (const { id, parents } of objs) {
881
+ if (parents.includes(parentType)) {
882
+ return id
883
+ }
884
+ }
885
+ }
886
+ }
887
+ }
888
+
889
+ export function applyOverrides(info, overrides) {
890
+ for (const name of overrides) {
891
+ if (hexColorPat.test(name)) {
892
+ info.color = name
893
+ info.category = ""
894
+ info.categoryIsDefault = false
895
+ } else if (overrideCategories.includes(name)) {
896
+ info.category = name
897
+ info.categoryIsDefault = false
898
+ } else if (overrideShapes.includes(name)) {
899
+ info.shape = name
900
+ info.shapeIsDefault = false
901
+ } else if (name === "loop") {
902
+ info.hasLoopArrow = true
903
+ } else if (name === "+" || name === "-") {
904
+ info.diff = name
905
+ } else if (name === "reset") {
906
+ info.categoryIsDefault = false
907
+ info.isReset = true
908
+ }
909
+ }
910
+ }
911
+
912
+ export function blockName(block) {
913
+ const words = []
914
+ for (const child of block.children) {
915
+ if (!child.isLabel) {
916
+ return
917
+ }
918
+ words.push(child.value)
919
+ }
920
+ return words.join(" ")
921
+ }