scratchblocks-plus 1.0.2 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * scratchblocks-plus v1.0.2
2
+ * scratchblocks-plus v1.1.1
3
3
  * https://luyifei2011.github.io/scratchblocks-plus
4
4
  * Make pictures of Scratch blocks from text.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * scratchblocks-plus v1.0.2
2
+ * scratchblocks-plus v1.1.1
3
3
  * https://luyifei2011.github.io/scratchblocks-plus
4
4
  * Make pictures of Scratch blocks from text.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * scratchblocks-plus v1.0.2
2
+ * scratchblocks-plus v1.1.1
3
3
  * https://luyifei2011.github.io/scratchblocks-plus
4
4
  * Make pictures of Scratch blocks from text.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * scratchblocks-plus v1.0.2
2
+ * scratchblocks-plus v1.1.1
3
3
  * https://luyifei2011.github.io/scratchblocks-plus
4
4
  * Make pictures of Scratch blocks from text.
5
5
  *
package/node-ssr.js ADDED
@@ -0,0 +1,81 @@
1
+ import init from "./index.js"
2
+ import { parse } from "./syntax/index.js"
3
+
4
+ let nodeWindow
5
+
6
+ try {
7
+ const { JSDOM } = await import("jsdom")
8
+ const dom = new JSDOM()
9
+ nodeWindow = dom.window
10
+ } catch {
11
+ try {
12
+ let createCanvas
13
+ const { DOMParser, XMLSerializer, DOMImplementation } =
14
+ await import("@xmldom/xmldom")
15
+ const nodeDocument = new DOMImplementation().createDocument(
16
+ "http://www.w3.org/2000/svg",
17
+ null,
18
+ null,
19
+ )
20
+ try {
21
+ createCanvas = (await import("@napi-rs/canvas")).createCanvas
22
+ } catch {
23
+ try {
24
+ createCanvas = (await import("canvas")).createCanvas
25
+ } catch {
26
+ // pass
27
+ }
28
+ }
29
+ if (createCanvas) {
30
+ const origCreateElement = nodeDocument.createElement.bind(nodeDocument)
31
+ nodeDocument.createElement = tagName =>
32
+ tagName === "canvas"
33
+ ? createCanvas(300, 150)
34
+ : origCreateElement(tagName)
35
+ }
36
+ nodeWindow = {
37
+ document: nodeDocument,
38
+ DOMParser,
39
+ XMLSerializer,
40
+ }
41
+ } catch {
42
+ // pass
43
+ }
44
+ }
45
+
46
+ const sb = init(nodeWindow)
47
+
48
+ export const {
49
+ allLanguages,
50
+ loadLanguages,
51
+ Label,
52
+ Icon,
53
+ Input,
54
+ Block,
55
+ Comment,
56
+ Script,
57
+ Document,
58
+ newView,
59
+ render,
60
+ } = sb
61
+
62
+ export { parse }
63
+
64
+ /**
65
+ * Parse Scratch block code and render it directly to an SVG XML string.
66
+ *
67
+ * @param {string} code - Scratch block source, e.g. "move (10) steps"
68
+ * @param {object} [options] - Same options accepted by scratchblocks.render()
69
+ * - style: "scratch3" | "scratch3-high-contrast" | "scratch2" (default: "scratch3")
70
+ * - languages: string[] (default: ["en"])
71
+ * - scale: number (default: 1)
72
+ * @returns {string} Complete SVG XML string
73
+ */
74
+ export function renderToSVGString(code, options = {}) {
75
+ options = { style: "scratch3", ...options }
76
+ const doc = parse(code, options)
77
+ const view = sb.newView(doc, options)
78
+ const svg = view.render()
79
+ svg.setAttribute("class", `scratchblocks-style-${options.style}`)
80
+ return view.exportSVGString()
81
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scratchblocks-plus",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "description": "Make pictures of Scratch blocks from text.",
5
5
  "license": "MIT",
6
6
  "author": "Lu Yifei",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "scripts": {
23
23
  "build": "rollup -c --environment buildTarget:PROD",
24
- "fmt": "prettier --cache --write *.js syntax/*.js scratch2/*.js scratch3/*.js locales-src/*.js snapshots/*.js snapshots/*.html tests/*.js",
24
+ "fmt": "prettier --cache --write *.js syntax/*.js scratch2/*.js scratch3/*.js locales-src/*.js snapshots/*.js tests/*.js",
25
25
  "lint:staged": "lint-staged",
26
26
  "lint": "eslint *.js syntax/*.js scratch2/*.js scratch3/*.js locales-src/*.js snapshots/*.js tests/*.js",
27
27
  "locales": "node locales-src/build-locales.js",
@@ -37,25 +37,46 @@
37
37
  "@babel/plugin-external-helpers": "^7.27.1",
38
38
  "@babel/preset-env": "^7.28.5",
39
39
  "@eslint/js": "^9.39.1",
40
+ "@napi-rs/canvas": "^0.1.96",
41
+ "@resvg/resvg-js": "^2.6.2",
40
42
  "@rollup/plugin-babel": "^6.1.0",
41
43
  "@rollup/plugin-json": "^6.1.0",
42
44
  "@rollup/plugin-terser": "^0.4.4",
45
+ "@xmldom/xmldom": "^0.8.11",
43
46
  "cross-env": "^10.1.0",
44
47
  "csso": "^5.0.5",
45
48
  "eslint": "^9.39.1",
46
- "express": "^5.1.0",
47
49
  "globals": "^16.5.0",
48
50
  "jest": "^30.2.0",
49
51
  "lint-staged": "^16.2.6",
50
52
  "prettier": "^3.6.2",
51
53
  "prettier-package-json": "^2.8.0",
52
- "puppeteer": "^24.30.0",
53
54
  "rollup": "^4.53.2",
54
55
  "rollup-plugin-license": "^3.6.0",
55
56
  "rollup-plugin-serve": "^3.0.0",
56
57
  "scratch-l10n": "^6.1.23",
57
58
  "scratch-translate-extension-languages": "^1.0.7"
58
59
  },
60
+ "peerDependencies": {
61
+ "@napi-rs/canvas": "*",
62
+ "@xmldom/xmldom": ">=0.8.0",
63
+ "canvas": ">=2.0.0",
64
+ "jsdom": ">=16.0.0"
65
+ },
66
+ "peerDependenciesMeta": {
67
+ "@xmldom/xmldom": {
68
+ "optional": true
69
+ },
70
+ "canvas": {
71
+ "optional": true
72
+ },
73
+ "@napi-rs/canvas": {
74
+ "optional": true
75
+ },
76
+ "jsdom": {
77
+ "optional": true
78
+ }
79
+ },
59
80
  "keywords": [
60
81
  "scratch"
61
82
  ],
@@ -24,6 +24,20 @@ const {
24
24
  darkFilter,
25
25
  } = style
26
26
 
27
+ // to be compatible with node.js xmldom
28
+ function addClass(el, className) {
29
+ if (el.classList) {
30
+ el.classList.add(className)
31
+ } else {
32
+ const current = el.getAttribute("class") || ""
33
+ const classes = current.split(/\s+/).filter(Boolean)
34
+ if (!classes.includes(className)) {
35
+ classes.push(className)
36
+ el.setAttribute("class", classes.join(" "))
37
+ }
38
+ }
39
+ }
40
+
27
41
  export class LabelView {
28
42
  constructor(label) {
29
43
  Object.assign(this, label)
@@ -184,7 +198,7 @@ class MatrixView {
184
198
  if (isFilled) {
185
199
  rect.setAttribute("fill", "#FFFFFF")
186
200
  } else {
187
- rect.classList.add(`sb-${parent.info.category}`)
201
+ addClass(rect, `sb-${parent.info.category}`)
188
202
  }
189
203
 
190
204
  elements.push(rect)
@@ -852,7 +866,9 @@ class DocumentView {
852
866
  * Build the element map by finding all elements with data-block-path
853
867
  */
854
868
  _buildElementMap() {
855
- if (!this.el) return
869
+ if (!this.el || !this.el.querySelectorAll) {
870
+ return
871
+ }
856
872
 
857
873
  this.elementMap.clear()
858
874
  const blocks = this.el.querySelectorAll("[data-block-path]")
@@ -881,7 +897,9 @@ class DocumentView {
881
897
  */
882
898
  highlightBlock(path, options = {}) {
883
899
  const el = this.getElementByPath(path)
884
- if (!el) return false
900
+ if (!el) {
901
+ return false
902
+ }
885
903
 
886
904
  // Add highlight class to the first child (the shape element)
887
905
  const shapeEl = el.firstElementChild
@@ -22,6 +22,34 @@ const {
22
22
  iconName,
23
23
  } = style
24
24
 
25
+ // to be compatible with node.js xmldom
26
+ function addClass(el, className) {
27
+ if (el.classList) {
28
+ el.classList.add(className)
29
+ } else {
30
+ const current = el.getAttribute("class") || ""
31
+ const classes = current.split(/\s+/).filter(Boolean)
32
+ if (!classes.includes(className)) {
33
+ classes.push(className)
34
+ el.setAttribute("class", classes.join(" "))
35
+ }
36
+ }
37
+ }
38
+
39
+ function removeClass(el, className) {
40
+ if (el.classList) {
41
+ el.classList.remove(className)
42
+ } else {
43
+ const current = el.getAttribute("class") || ""
44
+ const classes = current.split(/\s+/).filter(cls => cls !== className)
45
+ if (classes.length) {
46
+ el.setAttribute("class", classes.join(" "))
47
+ } else {
48
+ el.removeAttribute("class")
49
+ }
50
+ }
51
+ }
52
+
25
53
  export class LabelView {
26
54
  constructor(label) {
27
55
  Object.assign(this, label)
@@ -216,7 +244,7 @@ export class MatrixView {
216
244
  if (isFilled) {
217
245
  rect.setAttribute("fill", "#FFFFFF")
218
246
  } else {
219
- rect.classList.add(`sb3-${parent.info.category}`)
247
+ addClass(rect, `sb3-${parent.info.category}`)
220
248
  }
221
249
 
222
250
  elements.push(rect)
@@ -329,7 +357,7 @@ export class InputView {
329
357
  })
330
358
  }
331
359
  } else if (this.shape === "number-dropdown") {
332
- el.classList.add(`sb3-${parent.info.category}-alt`)
360
+ addClass(el, `sb3-${parent.info.category}-alt`)
333
361
 
334
362
  // custom colors
335
363
  if (parent.info.color) {
@@ -339,8 +367,8 @@ export class InputView {
339
367
  })
340
368
  }
341
369
  } else if (this.shape === "boolean") {
342
- el.classList.remove(`sb3-${parent.info.category}`)
343
- el.classList.add(`sb3-${parent.info.category}-dark`)
370
+ removeClass(el, `sb3-${parent.info.category}`)
371
+ addClass(el, `sb3-${parent.info.category}-dark`)
344
372
 
345
373
  // custom colors
346
374
  if (parent.info.color) {
@@ -969,7 +997,9 @@ class DocumentView {
969
997
  * Build the element map by finding all elements with data-block-path
970
998
  */
971
999
  _buildElementMap() {
972
- if (!this.el) return
1000
+ if (!this.el || !this.el.querySelectorAll) {
1001
+ return
1002
+ }
973
1003
 
974
1004
  this.elementMap.clear()
975
1005
  const blocks = this.el.querySelectorAll("[data-block-path]")
@@ -1000,7 +1030,9 @@ class DocumentView {
1000
1030
  */
1001
1031
  highlightBlock(path, options = {}) {
1002
1032
  const el = this.getElementByPath(path)
1003
- if (!el) return false
1033
+ if (!el) {
1034
+ return false
1035
+ }
1004
1036
 
1005
1037
  // Add highlight class to the first child (the shape element)
1006
1038
  const shapeEl = el.firstElementChild
@@ -2,7 +2,9 @@
2
2
 
3
3
  const common = `
4
4
  .sb3-label {
5
- font: 500 12pt Helvetica Neue, Helvetica, sans-serif;
5
+ font-weight: 500;
6
+ font-size: 12pt;
7
+ font-family: Helvetica Neue, Helvetica, sans-serif;
6
8
  }
7
9
 
8
10
  .sb3-literal-number,
@@ -63,7 +65,9 @@ const commonOverride = `
63
65
  }
64
66
  /* specificity */
65
67
  .sb3-comment-label, .sb3-label.sb3-comment-label {
66
- font: 400 12pt Helvetica Neue, Helvetica, sans-serif;
68
+ font-weight: 400;
69
+ font-size: 12pt;
70
+ font-family: Helvetica Neue, Helvetica, sans-serif;
67
71
  fill: #000;
68
72
  word-spacing: 0;
69
73
  }`