@elicdavis/node-flow 0.1.19 → 0.2.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 (55) hide show
  1. package/dist/index.d.mts +386 -227
  2. package/dist/index.d.ts +386 -227
  3. package/dist/index.js +1884 -1076
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1867 -1075
  6. package/dist/index.mjs.map +1 -1
  7. package/index.html +1134 -329
  8. package/package.json +1 -1
  9. package/src/connection.ts +30 -5
  10. package/src/contextMenu.ts +66 -34
  11. package/src/graph.ts +12 -10
  12. package/src/index.ts +26 -12
  13. package/src/markdown/entry.ts +11 -15
  14. package/src/markdown/syntaxParser.ts +2 -1
  15. package/src/node.ts +162 -111
  16. package/src/nodes/subsystem.ts +123 -29
  17. package/src/notes/note.ts +9 -1
  18. package/src/organize.ts +540 -111
  19. package/src/popup.ts +4 -2
  20. package/src/port.ts +83 -6
  21. package/src/styles/bevel.ts +31 -0
  22. package/src/styles/cursor.ts +1 -0
  23. package/src/styles/text.ts +4 -4
  24. package/src/theme.ts +179 -92
  25. package/src/types/text.ts +5 -1
  26. package/src/utils/color.ts +1 -1
  27. package/src/widgets/button.ts +52 -107
  28. package/src/widgets/color.ts +91 -150
  29. package/src/widgets/factory.ts +89 -46
  30. package/src/widgets/field.ts +99 -0
  31. package/src/widgets/image.ts +107 -165
  32. package/src/widgets/inplace.ts +84 -0
  33. package/src/widgets/number.ts +122 -154
  34. package/src/widgets/row.ts +138 -0
  35. package/src/widgets/slider.ts +101 -216
  36. package/src/widgets/string.ts +39 -130
  37. package/src/widgets/text.ts +81 -92
  38. package/src/widgets/toggle.ts +62 -207
  39. package/src/widgets/value.ts +127 -0
  40. package/src/widgets/widget.ts +180 -34
  41. package/test/context_menu.test.ts +82 -0
  42. package/test/cursor.test.ts +17 -0
  43. package/test/hidden_port.test.ts +125 -0
  44. package/test/hover_after_removal.test.ts +72 -0
  45. package/test/markdown.test.ts +2 -2
  46. package/test/markdown_code_block.test.ts +58 -0
  47. package/test/multi_input.test.ts +124 -0
  48. package/test/organize.test.ts +117 -1
  49. package/test/property_binding.test.ts +49 -0
  50. package/test/support.ts +175 -0
  51. package/test/theme.test.ts +86 -0
  52. package/test/widget_click.test.ts +111 -97
  53. package/test/widget_construction.test.ts +5 -4
  54. package/test/widgets.test.ts +333 -0
  55. package/test/wire_follows_camera.test.ts +57 -0
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@elicdavis/node-flow",
3
3
  "description": "build node graphs",
4
4
  "author": "Eli C Davis",
5
- "version": "0.1.19",
5
+ "version": "0.2.0",
6
6
  "license": "MIT",
7
7
  "keywords": [
8
8
  "nodes",
package/src/connection.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { FlowNode } from "./node";
2
2
  import { Port } from "./port";
3
- import { BoxCenter, InBox } from "./types/box";
3
+ import { Box, BoxCenter, InBox } from "./types/box";
4
4
  import { Vector2, Zero } from "./types/vector2";
5
5
  import { Clamp01 } from "./utils/math";
6
6
 
@@ -134,7 +134,7 @@ export class Connection {
134
134
  }
135
135
 
136
136
  if (this.#inNode !== null) {
137
- const inPortBox = this.#inNode.inputPortPosition(this.#inNodePortIndex)
137
+ const inPortBox = this.#inSlotBox();
138
138
  if (inPortBox === undefined) {
139
139
  return;
140
140
  }
@@ -202,7 +202,7 @@ export class Connection {
202
202
  this.clearOutput();
203
203
  }
204
204
 
205
- setInput(node: FlowNode, portIndex: number, replace?: boolean): void {
205
+ setInput(node: FlowNode, portIndex: number, replace?: boolean, insertAt?: number): void {
206
206
  this.#inNode = node;
207
207
  this.#inNodePortIndex = portIndex;
208
208
 
@@ -210,7 +210,7 @@ export class Connection {
210
210
  if(!!replace && port.connections().length > 0) {
211
211
  port.replaceConnection(this, 0);
212
212
  } else {
213
- port.addConnection(this);
213
+ port.addConnection(this, insertAt);
214
214
  }
215
215
  }
216
216
 
@@ -232,9 +232,34 @@ export class Connection {
232
232
  this.#outNodePortIndex = -1;
233
233
  }
234
234
 
235
+ /**
236
+ * The part of the input port this wire lands on: the whole port, or its
237
+ * own slot on a port holding several wires.
238
+ */
239
+ #inSlotBox(): Box | undefined {
240
+ if (this.#inNode === null) {
241
+ return undefined;
242
+ }
243
+ const box = this.#inNode.inputPortPosition(this.#inNodePortIndex);
244
+ const port = this.#inNode.inputPort(this.#inNodePortIndex);
245
+ const slots = port.slots();
246
+ if (box === undefined || slots === 1) {
247
+ return box;
248
+ }
249
+ const index = port.connections().indexOf(this);
250
+ if (index === -1) {
251
+ return box;
252
+ }
253
+ const height = box.Size.y / slots;
254
+ return {
255
+ Position: { x: box.Position.x, y: box.Position.y + (index * height) },
256
+ Size: { x: box.Size.x, y: height },
257
+ };
258
+ }
259
+
235
260
  mouseOverPort(mousePosition: Vector2): Port | null {
236
261
  if (this.#inNode !== null) {
237
- const inPortBox = this.#inNode.inputPortPosition(this.#inNodePortIndex)
262
+ const inPortBox = this.#inSlotBox();
238
263
  if (inPortBox !== undefined && InBox(inPortBox, mousePosition)) {
239
264
  return this.#inNode.inputPort(this.#inNodePortIndex)
240
265
  }
@@ -259,11 +259,23 @@ export class ContextMenu {
259
259
 
260
260
  #openSubMenu: ContextMenu | undefined;
261
261
 
262
+ #bounds: Box | null = null;
262
263
 
263
- public render(ctx: CanvasRenderingContext2D, pppp: Vector2, graphScale: number, mousePosition: Vector2 | undefined, openRight: boolean): ContextEntry | null {
264
+ #contains(point: Vector2): boolean {
265
+ if (this.#bounds !== null && InBox(this.#bounds, point)) {
266
+ return true;
267
+ }
268
+ return this.#openSubMenu !== undefined && this.#openSubMenu.#contains(point);
269
+ }
270
+
271
+ /**
272
+ * A submenu is anchored to its parent's left edge and told the parent's
273
+ * width, so it can open on whichever side has room.
274
+ */
275
+ public render(ctx: CanvasRenderingContext2D, anchor: Vector2, graphScale: number, mousePosition: Vector2 | undefined, openRight: boolean, parentWidth: number = 0): ContextEntry | null {
264
276
  const menuScale = 1.25;
265
277
  const scaledEntryHeight = menuScale * contextEntryHeight;
266
- const scaledEntryWidth = (menuScale * 40) + (this.#getMaxWidthForText(ctx, menuScale)); // contextEntryWidth;
278
+ const scaledEntryWidth = (menuScale * 40) + (this.#getMaxWidthForText(ctx, menuScale));
267
279
 
268
280
  let totalScaledHeight = 0
269
281
  for (let i = 0; i < this.#groups.Count(); i++) {
@@ -271,23 +283,41 @@ export class ContextMenu {
271
283
  }
272
284
  totalScaledHeight *= menuScale;
273
285
 
274
- const position = { x: 0, y: 0 };
275
- CopyVector2(position, pppp)
286
+ const canvasWidth = ctx.canvas.clientWidth;
287
+ const position = { x: anchor.x, y: anchor.y };
288
+ let opensRight = openRight;
276
289
 
277
- if (!openRight) {
278
- position.x -= scaledEntryWidth;
290
+ if (parentWidth === 0) {
291
+ if (!openRight) {
292
+ position.x -= scaledEntryWidth;
293
+ }
294
+ if (openRight && position.x + scaledEntryWidth > canvasWidth) {
295
+ position.x = canvasWidth - scaledEntryWidth;
296
+ opensRight = false;
297
+ }
298
+ } else {
299
+ const rightOfParent = anchor.x + parentWidth;
300
+ const leftOfParent = anchor.x - scaledEntryWidth;
301
+ const fitsRight = rightOfParent + scaledEntryWidth <= canvasWidth;
302
+ const fitsLeft = leftOfParent >= 0;
303
+ opensRight = openRight ? (fitsRight || !fitsLeft) : (!fitsLeft && fitsRight);
304
+ position.x = opensRight ? rightOfParent : leftOfParent;
305
+ position.x = Math.max(0, Math.min(position.x, canvasWidth - scaledEntryWidth));
279
306
  }
280
307
 
281
- // Clamp the position so it's not spilling off the canvas
282
308
  if (position.y + totalScaledHeight > ctx.canvas.clientHeight) {
283
309
  position.y = ctx.canvas.clientHeight - totalScaledHeight;
284
310
  }
311
+ position.y = Math.max(0, position.y);
285
312
 
286
- let submenuOpenRight = openRight;
287
- if (openRight && position.x + scaledEntryWidth > ctx.canvas.clientWidth) {
288
- position.x = ctx.canvas.clientWidth - scaledEntryWidth;
289
- submenuOpenRight = !submenuOpenRight;
290
- }
313
+ this.#bounds = {
314
+ Position: { x: position.x, y: position.y },
315
+ Size: { x: scaledEntryWidth, y: totalScaledHeight },
316
+ };
317
+
318
+ const pointer = mousePosition !== undefined && this.#openSubMenu !== undefined && this.#openSubMenu.#contains(mousePosition)
319
+ ? undefined
320
+ : mousePosition;
291
321
 
292
322
  this.#tempBox.Size.x = scaledEntryWidth;
293
323
  this.#tempBox.Size.y = scaledEntryHeight;
@@ -305,13 +335,27 @@ export class ContextMenu {
305
335
  this.#tempBox.Position.y,
306
336
  scaledEntryWidth,
307
337
  totalScaledHeight,
308
- 5 * menuScale
338
+ Theme.ContextMenu.BorderRadius * menuScale
309
339
  );
310
340
  ctx.fill();
311
341
  ctx.shadowBlur = 0;
312
342
 
313
343
  let mouseIsOver: ContextEntry | null = null;
314
- let subOpenedThisFrame = false;
344
+ if (pointer !== undefined) {
345
+ let row = 0;
346
+ for (let groupIndex = 0; groupIndex < this.#groups.Count(); groupIndex++) {
347
+ for (const entry of this.#groups.At(groupIndex).entries) {
348
+ this.#tempBox.Position.y = position.y + (scaledEntryHeight * row);
349
+ if (InBox(this.#tempBox, pointer)) {
350
+ mouseIsOver = entry;
351
+ this.#openSubMenu = entry.subMenu;
352
+ this.#submenuPosition.x = position.x;
353
+ this.#submenuPosition.y = this.#tempBox.Position.y;
354
+ }
355
+ row++;
356
+ }
357
+ }
358
+ }
315
359
 
316
360
  let optionsRendered = 0;
317
361
  for (let groupIndex = 0; groupIndex < this.#groups.Count(); groupIndex++) {
@@ -321,27 +365,13 @@ export class ContextMenu {
321
365
 
322
366
  this.#tempBox.Position.y = position.y + (scaledEntryHeight * optionsRendered);
323
367
 
324
- let entryMousedOver = false;
325
- if (mousePosition !== undefined && InBox(this.#tempBox, mousePosition)) {
326
- mouseIsOver = entry;
327
- entryMousedOver = true
328
- if (entry.subMenu !== undefined) {
329
- this.#openSubMenu = entry.subMenu;
330
- this.#submenuPosition.x = position.x
331
- this.#submenuPosition.y = this.#tempBox.Position.y;
332
- if (submenuOpenRight) {
333
- this.#submenuPosition.x += scaledEntryWidth
334
- }
335
- subOpenedThisFrame = true;
336
- } else {
337
- this.#openSubMenu = undefined;
338
- }
339
- }
368
+ const entryMousedOver = entry === mouseIsOver;
340
369
 
341
370
  const itemDisabled = entry.item !== undefined
342
371
  && entry.item.state() === ContextMenuItemState.Disabled;
343
372
 
344
- if (!itemDisabled && (entryMousedOver || (this.#openSubMenu !== undefined && entry.subMenu === this.#openSubMenu))) {
373
+ const highlighted = !itemDisabled && (entryMousedOver || (this.#openSubMenu !== undefined && entry.subMenu === this.#openSubMenu));
374
+ if (highlighted) {
345
375
  ctx.fillStyle = Theme.ContextMenu.HighlightColor;
346
376
  ctx.beginPath();
347
377
  ctx.roundRect(
@@ -349,7 +379,7 @@ export class ContextMenu {
349
379
  this.#tempBox.Position.y + (scaledEntryHeight / 10),
350
380
  scaledEntryWidth - (scaledEntryHeight / 5),
351
381
  scaledEntryHeight - (scaledEntryHeight / 5),
352
- 5 * menuScale
382
+ Theme.ContextMenu.BorderRadius * menuScale
353
383
  );
354
384
  ctx.fill();
355
385
  }
@@ -357,13 +387,15 @@ export class ContextMenu {
357
387
  this.#textStyle.setupStyle(ctx, menuScale);
358
388
  if (itemDisabled) {
359
389
  ctx.fillStyle = Theme.ContextMenu.DisabledFontColor;
390
+ } else if (highlighted) {
391
+ ctx.fillStyle = Theme.ContextMenu.HighlightFontColor;
360
392
  }
361
393
  ctx.fillText(entry.text, position.x + (scaledEntryHeight / 5), this.#tempBox.Position.y + (scaledEntryHeight / 2))
362
394
 
363
395
  // Render arrows
364
396
  if (entry.subMenu !== undefined) {
365
397
  ctx.beginPath()
366
- ctx.strokeStyle = Theme.ContextMenu.FontColor;
398
+ ctx.strokeStyle = highlighted ? Theme.ContextMenu.HighlightFontColor : Theme.ContextMenu.FontColor;
367
399
  ctx.lineWidth = 1 * menuScale;
368
400
  ctx.lineTo(position.x + scaledEntryWidth - (scaledEntryHeight / 2.5), this.#tempBox.Position.y + (scaledEntryHeight / 3))
369
401
  ctx.lineTo(position.x + scaledEntryWidth - (scaledEntryHeight / 4), this.#tempBox.Position.y + (scaledEntryHeight / 2))
@@ -388,7 +420,7 @@ export class ContextMenu {
388
420
  }
389
421
 
390
422
  if (this.#openSubMenu !== undefined) {
391
- const mouseOverSub = this.#openSubMenu.render(ctx, this.#submenuPosition, menuScale, mousePosition, submenuOpenRight)
423
+ const mouseOverSub = this.#openSubMenu.render(ctx, this.#submenuPosition, menuScale, mousePosition, opensRight, scaledEntryWidth)
392
424
  if (mouseOverSub !== null) {
393
425
  mouseIsOver = mouseOverSub;
394
426
  }
package/src/graph.ts CHANGED
@@ -23,17 +23,19 @@ import { Minimap, MinimapConfig } from './minimap';
23
23
 
24
24
  export type GraphRenderer = (canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, position: Vector2, scale: number) => void;
25
25
 
26
- function BuildBackgroundRenderer(backgroundColor: string): GraphRenderer {
26
+ function BuildBackgroundRenderer(backgroundColor: string | undefined): GraphRenderer {
27
27
  return (canvas: HTMLCanvasElement, context: CanvasRenderingContext2D, position: Vector2, scale: number): void => {
28
- context.fillStyle = backgroundColor;
28
+ context.fillStyle = backgroundColor ?? Theme.Graph.BackgroundColor;
29
29
  context.fillRect(0, 0, canvas.width, canvas.height);
30
30
 
31
- const alpha = Math.round(Clamp01(scale - 0.3) * 255)
31
+ const alpha = Clamp01(scale - 0.3);
32
32
  if (alpha <= 0) {
33
33
  return;
34
34
  }
35
35
 
36
- context.fillStyle = `rgba(41, 54, 57, ${alpha})`;
36
+ context.save();
37
+ context.globalAlpha = alpha;
38
+ context.fillStyle = Theme.Graph.DotColor;
37
39
  const spacing = 100;
38
40
  const pi2 = 2 * Math.PI
39
41
  const dotScale = 2 * scale;
@@ -46,6 +48,7 @@ function BuildBackgroundRenderer(backgroundColor: string): GraphRenderer {
46
48
  context.fill();
47
49
  }
48
50
  }
51
+ context.restore();
49
52
  }
50
53
  }
51
54
 
@@ -75,7 +78,7 @@ interface OpenQuickMenu {
75
78
  Position: Vector2
76
79
  }
77
80
 
78
- class GraphView {
81
+ export class GraphView {
79
82
 
80
83
  #subsystems: Array<GraphSubsystem>;
81
84
 
@@ -138,9 +141,9 @@ class GraphView {
138
141
  const results: RenderResults = {};
139
142
  for (let i = 0; i < this.#subsystems.length; i++) {
140
143
  TimeExecution("Render_Subsystem_" + i, () => {
141
- let results = this.#subsystems[i].render(ctx, camera, mousePosition);
142
- if (results?.cursorStyle) {
143
- results.cursorStyle = results?.cursorStyle;
144
+ const layer = this.#subsystems[i].render(ctx, camera, mousePosition);
145
+ if (layer?.cursorStyle) {
146
+ results.cursorStyle = layer.cursorStyle;
144
147
  }
145
148
  })
146
149
  }
@@ -224,8 +227,7 @@ export class NodeFlowGraph {
224
227
  if (config?.backgroundRenderer !== undefined) {
225
228
  this.#backgroundRenderer = config?.backgroundRenderer
226
229
  } else {
227
- const backgroundColor = config?.backgroundColor === undefined ? Theme.Graph.BackgroundColor : config.backgroundColor;
228
- this.#backgroundRenderer = BuildBackgroundRenderer(backgroundColor);
230
+ this.#backgroundRenderer = BuildBackgroundRenderer(config?.backgroundColor);
229
231
  }
230
232
 
231
233
  this.#minimap = new Minimap(config?.minimap);
package/src/index.ts CHANGED
@@ -4,8 +4,9 @@ export * from './node';
4
4
  import { ContextMenuConfig, ContextMenuItemConfig, ContextMenuItemState } from './contextMenu';
5
5
  export { ContextMenuConfig, ContextMenuItemConfig, ContextMenuItemState };
6
6
 
7
- import { Theme } from './theme';
8
- export { Theme };
7
+ import { Theme, DefaultTheme, setTheme, themeVersion, onThemeChange, ThemeConfig, ThemeValues, BevelColors } from './theme';
8
+ export { Theme, DefaultTheme, setTheme, themeVersion, onThemeChange };
9
+ export type { ThemeConfig, ThemeValues, BevelColors };
9
10
 
10
11
  import { Publisher, PublisherConfig } from './nodes/publisher';
11
12
  export { Publisher, PublisherConfig };
@@ -53,30 +54,43 @@ import { NodeCreatedCallback, NodeFactory, NodeFactoryConfig } from './nodes/fac
53
54
  export { NodeCreatedCallback, NodeFactory, NodeFactoryConfig };
54
55
 
55
56
  // Widgets
56
- import { Widget } from './widgets/widget';
57
- import { NumberWidget, NumberWidgetConfig } from './widgets/number';
57
+ import { Widget, WidgetBase, WidgetPointer } from './widgets/widget';
58
+ import { ValueWidget, ValueWidgetConfig } from './widgets/value';
59
+ import { NumberWidget, NumberWidgetConfig, formatNumber } from './widgets/number';
58
60
  import { ColorWidget, ColorWidgetConfig } from './widgets/color';
59
61
  import { StringWidget, StringWidgetConfig } from './widgets/string';
60
62
  import { TextWidget, TextWidgetConfig } from './widgets/text';
61
63
  import { ButtonWidget, ButtonWidgetConfig } from './widgets/button';
62
- import { ToggleWidget, ToggleWidgetConfig, ToggleStyleConfig } from './widgets/toggle';
64
+ import { ToggleWidget, ToggleWidgetConfig } from './widgets/toggle';
63
65
  import { SliderWidget, SliderWidgetConfig } from './widgets/slider';
64
66
  import { ImageWidget, ImageWidgetConfig } from './widgets/image';
67
+ import { RowWidget, RowWidgetConfig } from './widgets/row';
68
+ import { drawField, drawFieldBox, drawFieldText, FieldLook, widgetFont } from './widgets/field';
69
+ import { editInPlace, InPlaceEditConfig } from './widgets/inplace';
65
70
  export {
66
- Widget,
71
+ WidgetBase, ValueWidget,
67
72
  NumberWidget, ColorWidget, StringWidget, TextWidget,
68
- ButtonWidget, ToggleWidget, SliderWidget, ImageWidget
73
+ ButtonWidget, ToggleWidget, SliderWidget, ImageWidget, RowWidget,
74
+ drawField, drawFieldBox, drawFieldText, widgetFont, editInPlace, formatNumber,
69
75
  };
70
76
  export type {
77
+ Widget, WidgetPointer, ValueWidgetConfig, FieldLook, InPlaceEditConfig,
71
78
  NumberWidgetConfig, ColorWidgetConfig, StringWidgetConfig, TextWidgetConfig,
72
- ButtonWidgetConfig, ToggleWidgetConfig, ToggleStyleConfig, SliderWidgetConfig,
73
- ImageWidgetConfig
79
+ ButtonWidgetConfig, ToggleWidgetConfig, SliderWidgetConfig,
80
+ ImageWidgetConfig, RowWidgetConfig,
74
81
  };
75
82
 
76
- import { GlobalWidgetFactory, WidgetBuilder, WidgetFactory } from './widgets/factory';
83
+ import {
84
+ GlobalWidgetFactory, WidgetBuilder, WidgetFactory,
85
+ WidgetConfig, WidgetConfigs, WidgetTypes, RowWidgetFactoryConfig,
86
+ } from './widgets/factory';
77
87
  export { GlobalWidgetFactory, WidgetFactory };
78
- export type { WidgetBuilder };
88
+ export type { WidgetBuilder, WidgetConfig, WidgetConfigs, WidgetTypes, RowWidgetFactoryConfig };
89
+
90
+ import { CursorStyle } from './styles/cursor';
91
+ export { CursorStyle };
79
92
 
80
- import { Box } from './types/box';
93
+ import { Box, InBox } from './types/box';
81
94
  import { Vector2 } from './types/vector2';
95
+ export { InBox };
82
96
  export type { Box, Vector2 };
@@ -82,28 +82,26 @@ export class CodeBlockEntry {
82
82
  render(ctx: CanvasRenderingContext2D, position: Vector2, scale: number, maxWidth: number): number {
83
83
  this.#calculateLayout(ctx, maxWidth);
84
84
 
85
- let padding = Theme.Note.CodeBlock.Padding * scale;
85
+ const padding = Theme.Note.CodeBlock.Padding * scale;
86
+ const lineCount = this.#calculatedEntries.Count();
87
+ const lastTop = lineCount === 0 ? 0 : this.#calculatedPositions.At(lineCount - 1).y;
88
+ const contentHeight = lineCount === 0 ? 0 : (lastTop + this.#text.style().getSize()) * scale;
89
+ const height = contentHeight + (padding * 2);
86
90
 
87
- let max = 0;
88
- for (let i = 0; i < this.#calculatedEntries.Count(); i++) {
89
- const pos = this.#calculatedPositions.At(i);
90
- max = Math.max(max, pos.y * scale)
91
- }
92
-
93
- const y = position.y + max + (scale * 5)
94
91
  ctx.fillStyle = Theme.Note.CodeBlock.BackgroundColor;
95
92
  ctx.beginPath();
96
93
  ctx.roundRect(
97
94
  position.x,
98
95
  position.y,
99
96
  maxWidth * scale,
100
- max + (padding * 2),
97
+ height,
101
98
  Theme.Note.CodeBlock.BorderRadius * scale
102
99
  );
103
100
  ctx.fill();
104
101
 
105
-
106
- for (let i = 0; i < this.#calculatedEntries.Count(); i++) {
102
+ const baseline = ctx.textBaseline;
103
+ ctx.textBaseline = "top";
104
+ for (let i = 0; i < lineCount; i++) {
107
105
  const entry = this.#calculatedEntries.At(i);
108
106
  const pos = this.#calculatedPositions.At(i);
109
107
 
@@ -111,12 +109,10 @@ export class CodeBlockEntry {
111
109
  x: (pos.x * scale) + position.x + padding,
112
110
  y: (pos.y * scale) + position.y + padding
113
111
  });
114
-
115
- max = Math.max(max, pos.y * scale)
116
112
  }
113
+ ctx.textBaseline = baseline;
117
114
 
118
-
119
- return max + (padding * 2);
115
+ return height;
120
116
  }
121
117
  }
122
118
 
@@ -314,9 +314,10 @@ export class MarkdownSyntaxParser {
314
314
  this.#inc(); // puts us on the 3rd
315
315
  this.#inc(); // puts us on the next token
316
316
 
317
- const codeBlockText = this.#originalText.substring(start as number, end);
317
+ const codeBlockText = this.#originalText.substring(start as number, end).replace(/\r?\n$/, "");
318
318
  const entryText = new Text(codeBlockText);
319
319
  this.#assignStandardStyling([entryText])
320
+ entryText.setColor(Theme.Note.CodeBlock.FontColor);
320
321
  return new CodeBlockEntry(entryText)
321
322
  }
322
323