web based infinite canvas
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: markdown data model

* export md as svg (foreignObject)

* rendering of md

+1563 -9
+34 -1
packages/core/src/export.ts
··· 1 1 import { shapeBounds } from "./geom"; 2 2 import type { Box2 } from "./math"; 3 3 import { Box2 as Box2Ops } from "./math"; 4 - import type { ArrowShape, EllipseShape, LineShape, RectShape, ShapeRecord, TextShape } from "./model"; 4 + import type { ArrowShape, EllipseShape, LineShape, MarkdownShape, RectShape, ShapeRecord, TextShape } from "./model"; 5 5 import type { EditorState } from "./reactivity"; 6 6 import { getSelectedShapes, getShapesOnCurrentPage } from "./reactivity"; 7 7 ··· 168 168 case "text": { 169 169 return textToSVG(shape, transform); 170 170 } 171 + case "markdown": { 172 + return markdownToSVG(shape, transform); 173 + } 171 174 default: { 172 175 return null; 173 176 } ··· 245 248 return `<text transform="${transform}" font-size="${fontSize}" font-family="${escapeXML(fontFamily)}" fill="${ 246 249 escapeXML(color) 247 250 }">${escapeXML(text)}</text>`; 251 + } 252 + 253 + /** 254 + * Export markdown shape as SVG foreignObject 255 + * 256 + * Uses foreignObject to embed HTML for markdown rendering. 257 + * 258 + * For better compatibility, the markdown is exported as plain text with basic formatting preserved. 259 + */ 260 + function markdownToSVG(shape: MarkdownShape, transform: string): string { 261 + const { md, w, h, fontSize, fontFamily, color, bg, border } = shape.props; 262 + const width = w; 263 + const height = h ?? fontSize * 10; 264 + 265 + const bgStyle = bg ? `background: ${escapeXML(bg)};` : "background: white;"; 266 + const borderStyle = border ? `border: 1px solid ${escapeXML(border)};` : ""; 267 + 268 + const escapedMarkdown = escapeXML(md); 269 + 270 + return [ 271 + `<foreignObject transform="${transform}" width="${width}" height="${height}">`, 272 + ` <div xmlns="http://www.w3.org/1999/xhtml" style="${bgStyle}${borderStyle} padding: 8px; font-size: ${fontSize}px; font-family: ${ 273 + escapeXML(fontFamily) 274 + }; color: ${ 275 + escapeXML(color) 276 + }; width: 100%; height: 100%; overflow: auto; white-space: pre-wrap; box-sizing: border-box;">`, 277 + ` ${escapedMarkdown}`, 278 + ` </div>`, 279 + `</foreignObject>`, 280 + ].join("\n"); 248 281 } 249 282 250 283 /**
+46
packages/core/src/geom.ts
··· 6 6 BrushConfig, 7 7 EllipseShape, 8 8 LineShape, 9 + MarkdownShape, 9 10 RectShape, 10 11 ShapeRecord, 11 12 StrokePoint, ··· 43 44 } 44 45 case "stroke": { 45 46 return strokeBounds(shape); 47 + } 48 + case "markdown": { 49 + return markdownBounds(shape); 46 50 } 47 51 } 48 52 } ··· 140 144 } 141 145 142 146 /** 147 + * Get bounds for a markdown block shape 148 + */ 149 + function markdownBounds(shape: MarkdownShape): Box2 { 150 + const { w, h, fontSize } = shape.props; 151 + const { x, y, rot } = shape; 152 + 153 + const width = w; 154 + const height = h ?? fontSize * 10; 155 + 156 + if (rot === 0) { 157 + return Box2Ops.create(x, y, x + width, y + height); 158 + } 159 + 160 + const corners = [{ x: 0, y: 0 }, { x: width, y: 0 }, { x: width, y: height }, { x: 0, y: height }]; 161 + const rotatedCorners = corners.map((corner) => rotatePoint(corner, rot)); 162 + const translatedCorners = rotatedCorners.map((corner) => ({ x: corner.x + x, y: corner.y + y })); 163 + return Box2Ops.fromPoints(translatedCorners); 164 + } 165 + 166 + /** 143 167 * Compute outline polygon points for a stroke using perfect-freehand 144 168 * 145 169 * @param points - Array of stroke points [x, y, pressure?] ··· 324 348 } 325 349 326 350 /** 351 + * Check if a point is inside a markdown block shape 352 + * 353 + * @param p - Point in world coordinates 354 + * @param shape - Markdown block shape 355 + * @returns True if point is inside the markdown block bounds 356 + */ 357 + export function pointInMarkdown(p: Vec2, shape: MarkdownShape): boolean { 358 + const { x, y, rot } = shape; 359 + const { w, h, fontSize } = shape.props; 360 + const localP = worldToLocal(p, x, y, rot); 361 + const width = w; 362 + const height = h ?? fontSize * 10; 363 + return localP.x >= 0 && localP.x <= width && localP.y >= 0 && localP.y <= height; 364 + } 365 + 366 + /** 327 367 * Check if a point is inside a polygon using ray casting algorithm 328 368 * 329 369 * @param p - Point to test ··· 431 471 } 432 472 case "text": { 433 473 if (pointInText(worldPoint, shape)) { 474 + return shape.id; 475 + } 476 + break; 477 + } 478 + case "markdown": { 479 + if (pointInMarkdown(worldPoint, shape)) { 434 480 return shape.id; 435 481 } 436 482 break;
+44 -3
packages/core/src/model.ts
··· 68 68 export type TextProps = { text: string; fontSize: number; fontFamily: string; color: string; w?: number }; 69 69 70 70 /** 71 + * Markdown block properties 72 + * - md: markdown source text 73 + * - w: fixed width (required for layout) 74 + * - h: auto-computed height from layout (optional override) 75 + * - style: font and color settings 76 + */ 77 + export type MarkdownProps = { 78 + md: string; 79 + w: number; 80 + h?: number; 81 + fontSize: number; 82 + fontFamily: string; 83 + color: string; 84 + bg?: string; 85 + border?: string; 86 + }; 87 + 88 + /** 71 89 * Point with optional pressure value (0-1) 72 90 * Format: [x, y, pressure?] 73 91 */ ··· 97 115 */ 98 116 export type StrokeProps = { points: StrokePoint[]; style: StrokeStyle; brush: BrushConfig }; 99 117 100 - export type ShapeType = "rect" | "ellipse" | "line" | "arrow" | "text" | "stroke"; 101 - 118 + export type ShapeType = "rect" | "ellipse" | "line" | "arrow" | "text" | "stroke" | "markdown"; 102 119 export type BaseShape = { id: string; type: ShapeType; pageId: string; x: number; y: number; rot: number }; 103 120 export type RectShape = BaseShape & { type: "rect"; props: RectProps }; 104 121 export type EllipseShape = BaseShape & { type: "ellipse"; props: EllipseProps }; ··· 106 123 export type ArrowShape = BaseShape & { type: "arrow"; props: ArrowProps }; 107 124 export type TextShape = BaseShape & { type: "text"; props: TextProps }; 108 125 export type StrokeShape = BaseShape & { type: "stroke"; props: StrokeProps }; 126 + export type MarkdownShape = BaseShape & { type: "markdown"; props: MarkdownProps }; 109 127 110 - export type ShapeRecord = RectShape | EllipseShape | LineShape | ArrowShape | TextShape | StrokeShape; 128 + export type ShapeRecord = RectShape | EllipseShape | LineShape | ArrowShape | TextShape | StrokeShape | MarkdownShape; 111 129 112 130 export const ShapeRecord = { 113 131 /** ··· 153 171 }, 154 172 155 173 /** 174 + * Create a markdown block shape 175 + */ 176 + createMarkdown(pageId: string, x: number, y: number, properties: MarkdownProps, id?: string): MarkdownShape { 177 + return { id: id ?? createId("shape"), type: "markdown", pageId, x, y, rot: 0, props: properties }; 178 + }, 179 + 180 + /** 156 181 * Clone a shape record 157 182 */ 158 183 clone(shape: ShapeRecord): ShapeRecord { ··· 179 204 label: shape.props.label ? { ...shape.props.label } : undefined, 180 205 }, 181 206 }; 207 + } 208 + if (shape.type === "markdown") { 209 + return { ...shape, props: { ...shape.props } }; 182 210 } 183 211 return { ...shape, props: { ...shape.props } } as ShapeRecord; 184 212 }, ··· 344 372 } 345 373 if (shape.props.style.opacity < 0 || shape.props.style.opacity > 1) { 346 374 errors.push(`Stroke shape '${shapeId}' has invalid opacity`); 375 + } 376 + 377 + break; 378 + } 379 + case "markdown": { 380 + if (shape.props.fontSize <= 0) { 381 + errors.push(`Markdown shape '${shapeId}' has invalid fontSize`); 382 + } 383 + if (shape.props.w <= 0) { 384 + errors.push(`Markdown shape '${shapeId}' has invalid width`); 385 + } 386 + if (shape.props.h !== undefined && shape.props.h <= 0) { 387 + errors.push(`Markdown shape '${shapeId}' has invalid height`); 347 388 } 348 389 349 390 break;
+1 -1
packages/core/src/reactivity.ts
··· 12 12 import type { Document, PageRecord, ShapeRecord } from "./model"; 13 13 import { Document as DocumentOps } from "./model"; 14 14 15 - export type ToolId = "select" | "rect" | "ellipse" | "line" | "arrow" | "text" | "pen"; 15 + export type ToolId = "select" | "rect" | "ellipse" | "line" | "arrow" | "text" | "pen" | "markdown"; 16 16 17 17 export type BindingPreview = { arrowId: string; targetShapeId: string; handle: "start" | "end" }; 18 18
+1
packages/core/src/tools/index.ts
··· 1 1 export * from "./base"; 2 + export * from "./markdown"; 2 3 export * from "./pen"; 3 4 export * from "./select"; 4 5 export * from "./shape";
+66
packages/core/src/tools/markdown.ts
··· 1 + import type { Action } from "../actions"; 2 + import { createId, ShapeRecord } from "../model"; 3 + import type { EditorState, ToolId } from "../reactivity"; 4 + import { getCurrentPage } from "../reactivity"; 5 + import type { Tool } from "./base"; 6 + 7 + /** 8 + * Markdown tool creates markdown block shapes on click 9 + * 10 + * Features: 11 + * - Click to create a markdown block at the pointer position 12 + * - Block is created with default content and dimensions 13 + * - Shape is immediately selected after creation 14 + */ 15 + export class MarkdownTool implements Tool { 16 + readonly id: ToolId = "markdown"; 17 + 18 + onEnter(state: EditorState): EditorState { 19 + return state; 20 + } 21 + 22 + onExit(state: EditorState): EditorState { 23 + return state; 24 + } 25 + 26 + onAction(state: EditorState, action: Action): EditorState { 27 + switch (action.type) { 28 + case "pointer-down": { 29 + return this.handlePointerDown(state, action); 30 + } 31 + default: { 32 + return state; 33 + } 34 + } 35 + } 36 + 37 + private handlePointerDown(state: EditorState, action: Action): EditorState { 38 + if (action.type !== "pointer-down") return state; 39 + 40 + const currentPage = getCurrentPage(state); 41 + if (!currentPage) return state; 42 + 43 + const shapeId = createId("shape"); 44 + 45 + const shape = ShapeRecord.createMarkdown(currentPage.id, action.world.x, action.world.y, { 46 + md: "# Markdown\n\nEdit me...", 47 + w: 300, 48 + h: 200, 49 + fontSize: 16, 50 + fontFamily: "sans-serif", 51 + color: "#1f2933", 52 + }, shapeId); 53 + 54 + const newPage = { ...currentPage, shapeIds: [...currentPage.shapeIds, shapeId] }; 55 + 56 + return { 57 + ...state, 58 + doc: { 59 + ...state.doc, 60 + shapes: { ...state.doc.shapes, [shapeId]: shape }, 61 + pages: { ...state.doc.pages, [currentPage.id]: newPage }, 62 + }, 63 + ui: { ...state.ui, selectionIds: [shapeId] }, 64 + }; 65 + } 66 + }
+10 -2
packages/core/src/tools/select.ts
··· 576 576 pointer: Vec2, 577 577 handle: HandleKind, 578 578 ): ShapeRecord | null { 579 - if (initial.type !== "rect" && initial.type !== "ellipse" && initial.type !== "text") { 579 + if ( 580 + initial.type !== "rect" && initial.type !== "ellipse" && initial.type !== "text" && initial.type !== "markdown" 581 + ) { 580 582 return null; 581 583 } 582 584 let minX = bounds.min.x; ··· 631 633 632 634 if (initial.type === "text") { 633 635 return { ...initial, x: minX, y: minY, props: { ...initial.props, w: width } }; 636 + } 637 + 638 + if (initial.type === "markdown") { 639 + return { ...initial, x: minX, y: minY, props: { ...initial.props, w: width, h: height } }; 634 640 } 635 641 636 642 // @ts-expect-error union mismatch ··· 756 762 if (!this.toolState.rotationCenter || this.toolState.rotationStartAngle === null) { 757 763 return null; 758 764 } 759 - if (initial.type !== "rect" && initial.type !== "ellipse" && initial.type !== "text") { 765 + if ( 766 + initial.type !== "rect" && initial.type !== "ellipse" && initial.type !== "text" && initial.type !== "markdown" 767 + ) { 760 768 return null; 761 769 } 762 770 const currentAngle = Math.atan2(
+394
packages/core/tests/markdown.test.ts
··· 1 + import { describe, expect, it } from "vitest"; 2 + import { pointInMarkdown, shapeBounds } from "../src/geom"; 3 + import type { MarkdownProps } from "../src/model"; 4 + import { Document, PageRecord, ShapeRecord, validateDoc } from "../src/model"; 5 + import { EditorState as EditorStateOps } from "../src/reactivity"; 6 + 7 + const createProps = (overrides?: Partial<MarkdownProps>) => ({ 8 + md: "# Hello World", 9 + w: 300, 10 + h: 200, 11 + fontSize: 16, 12 + fontFamily: "sans-serif", 13 + color: "#1f2933", 14 + ...overrides, 15 + }); 16 + 17 + describe("MarkdownShape", () => { 18 + const pageId = "page:test"; 19 + 20 + describe("createMarkdown", () => { 21 + it("should create a markdown shape with generated ID", () => { 22 + const props = createProps(); 23 + const shape = ShapeRecord.createMarkdown(pageId, 10, 20, props); 24 + 25 + expect(shape.id).toMatch(/^shape:/); 26 + expect(shape.type).toBe("markdown"); 27 + expect(shape.pageId).toBe(pageId); 28 + expect(shape.x).toBe(10); 29 + expect(shape.y).toBe(20); 30 + expect(shape.rot).toBe(0); 31 + expect(shape.props).toEqual(props); 32 + }); 33 + 34 + it("should create a markdown shape with custom ID", () => { 35 + const props = createProps({ md: "# Test", color: "#000" }); 36 + const shape = ShapeRecord.createMarkdown(pageId, 10, 20, props, "shape:custom"); 37 + expect(shape.id).toBe("shape:custom"); 38 + }); 39 + 40 + it("should create a markdown shape with optional bg and border", () => { 41 + const props = createProps({ md: "# Styled", color: "#000", bg: "#ffffff", border: "#cccccc" }); 42 + const shape = ShapeRecord.createMarkdown(pageId, 0, 0, props); 43 + expect(shape.props.bg).toBe("#ffffff"); 44 + expect(shape.props.border).toBe("#cccccc"); 45 + }); 46 + 47 + it("should create a markdown shape without height (auto-computed)", () => { 48 + const props = createProps({ md: "# Auto Height", w: 300, h: undefined, color: "#000" }); 49 + const shape = ShapeRecord.createMarkdown(pageId, 0, 0, props); 50 + expect(shape.props.h).toBeUndefined(); 51 + }); 52 + 53 + it.each([{ md: "# Heading\n\nParagraph", w: 400, h: 300, fontSize: 18 }, { 54 + md: "- List item 1\n- List item 2", 55 + w: 200, 56 + h: 150, 57 + fontSize: 14, 58 + }, { md: "```\ncode block\n```", w: 350, h: 250, fontSize: 12 }])( 59 + "should create markdown with various content: %o", 60 + ({ md, w, h, fontSize }) => { 61 + const props: MarkdownProps = { md, w, h, fontSize, fontFamily: "sans-serif", color: "#000" }; 62 + const shape = ShapeRecord.createMarkdown(pageId, 0, 0, props); 63 + 64 + expect(shape.props.md).toBe(md); 65 + expect(shape.props.w).toBe(w); 66 + expect(shape.props.h).toBe(h); 67 + }, 68 + ); 69 + }); 70 + 71 + describe("clone", () => { 72 + it("should clone a markdown shape", () => { 73 + const props = createProps({ md: "# Clone Test", color: "#000" }); 74 + const shape = ShapeRecord.createMarkdown(pageId, 10, 20, props); 75 + const cloned = ShapeRecord.clone(shape); 76 + 77 + expect(cloned).toEqual(shape); 78 + expect(cloned).not.toBe(shape); 79 + expect(cloned.props).not.toBe(shape.props); 80 + }); 81 + 82 + it("should deep clone props", () => { 83 + const props = createProps({ md: "# Original", fontFamily: "sans-serif", color: "#000" }); 84 + const shape = ShapeRecord.createMarkdown(pageId, 10, 20, props); 85 + const cloned = ShapeRecord.clone(shape); 86 + 87 + if (cloned.type === "markdown") { 88 + cloned.props.md = "# Modified"; 89 + cloned.props.w = 400; 90 + } 91 + 92 + expect(shape.props.md).toBe("# Original"); 93 + expect(shape.props.w).toBe(300); 94 + }); 95 + }); 96 + 97 + describe("geometry", () => { 98 + describe("shapeBounds", () => { 99 + it("should compute bounds for markdown shape without rotation", () => { 100 + const shape = ShapeRecord.createMarkdown(pageId, 10, 20, createProps({ md: "# Test", color: "#000" })); 101 + const bounds = shapeBounds(shape); 102 + expect(bounds.min.x).toBe(10); 103 + expect(bounds.min.y).toBe(20); 104 + expect(bounds.max.x).toBe(310); 105 + expect(bounds.max.y).toBe(220); 106 + }); 107 + 108 + it("should compute bounds for markdown shape with auto height", () => { 109 + const shape = ShapeRecord.createMarkdown(pageId, 0, 0, createProps({ md: "# Test", color: "#000" })); 110 + const bounds = shapeBounds(shape); 111 + expect(bounds.min.x).toBe(0); 112 + expect(bounds.min.y).toBe(0); 113 + expect(bounds.max.x).toBe(300); 114 + expect(bounds.max.y).toBe(160); 115 + }); 116 + 117 + it("should compute rotated bounds correctly", () => { 118 + const shape = ShapeRecord.createMarkdown( 119 + pageId, 120 + 100, 121 + 100, 122 + createProps({ md: "# Test", w: 200, h: 100, color: "#000" }), 123 + ); 124 + shape.rot = Math.PI / 4; 125 + 126 + const bounds = shapeBounds(shape); 127 + 128 + expect(bounds.min.x).toBeLessThan(101); 129 + expect(bounds.min.y).toBeLessThan(101); 130 + expect(bounds.max.x).toBeGreaterThan(200); 131 + expect(bounds.max.y).toBeGreaterThan(150); 132 + }); 133 + }); 134 + 135 + describe("pointInMarkdown", () => { 136 + it("should return true for point inside markdown block", () => { 137 + const shape = ShapeRecord.createMarkdown(pageId, 10, 20, createProps({ md: "# Test", color: "#000" })); 138 + expect(pointInMarkdown({ x: 100, y: 100 }, shape)).toBe(true); 139 + }); 140 + 141 + it("should return false for point outside markdown block", () => { 142 + const shape = ShapeRecord.createMarkdown(pageId, 10, 20, createProps({ md: "# Test", color: "#000" })); 143 + expect(pointInMarkdown({ x: 400, y: 100 }, shape)).toBe(false); 144 + expect(pointInMarkdown({ x: 100, y: 300 }, shape)).toBe(false); 145 + }); 146 + 147 + it("should handle edge cases on bounds", () => { 148 + const shape = ShapeRecord.createMarkdown(pageId, 10, 20, createProps({ md: "# Test", color: "#000" })); 149 + expect(pointInMarkdown({ x: 10, y: 20 }, shape)).toBe(true); 150 + expect(pointInMarkdown({ x: 310, y: 220 }, shape)).toBe(true); 151 + expect(pointInMarkdown({ x: 9, y: 20 }, shape)).toBe(false); 152 + expect(pointInMarkdown({ x: 311, y: 220 }, shape)).toBe(false); 153 + }); 154 + }); 155 + }); 156 + 157 + describe("validation", () => { 158 + it("should validate markdown shape with all required fields", () => { 159 + const doc = Document.create(); 160 + const page = PageRecord.create("Page 1", "page1"); 161 + const shape = ShapeRecord.createMarkdown("page1", 0, 0, createProps({ md: "# Valid", color: "#000" }), "shape1"); 162 + 163 + page.shapeIds = ["shape1"]; 164 + doc.pages = { page1: page }; 165 + doc.shapes = { shape1: shape }; 166 + 167 + const result = validateDoc(doc); 168 + expect(result.ok).toBe(true); 169 + }); 170 + 171 + it("should reject markdown with invalid fontSize", () => { 172 + const doc = Document.create(); 173 + const page = PageRecord.create("Page 1", "page1"); 174 + const shape = ShapeRecord.createMarkdown( 175 + "page1", 176 + 0, 177 + 0, 178 + createProps({ md: "# Test", fontSize: 0, color: "#000" }), 179 + "shape1", 180 + ); 181 + 182 + page.shapeIds = ["shape1"]; 183 + doc.pages = { page1: page }; 184 + doc.shapes = { shape1: shape }; 185 + 186 + const result = validateDoc(doc); 187 + 188 + expect(result.ok).toBe(false); 189 + if (!result.ok) { 190 + expect(result.errors).toContain("Markdown shape 'shape1' has invalid fontSize"); 191 + } 192 + }); 193 + 194 + it("should reject markdown with invalid width", () => { 195 + const doc = Document.create(); 196 + const page = PageRecord.create("Page 1", "page1"); 197 + const shape = ShapeRecord.createMarkdown( 198 + "page1", 199 + 0, 200 + 0, 201 + createProps({ md: "# Test", w: 0, h: 200, color: "#000" }), 202 + "shape1", 203 + ); 204 + 205 + page.shapeIds = ["shape1"]; 206 + doc.pages = { page1: page }; 207 + doc.shapes = { shape1: shape }; 208 + 209 + const result = validateDoc(doc); 210 + 211 + expect(result.ok).toBe(false); 212 + if (!result.ok) { 213 + expect(result.errors).toContain("Markdown shape 'shape1' has invalid width"); 214 + } 215 + }); 216 + 217 + it("should reject markdown with negative height", () => { 218 + const doc = Document.create(); 219 + const page = PageRecord.create("Page 1", "page1"); 220 + const shape = ShapeRecord.createMarkdown( 221 + "page1", 222 + 0, 223 + 0, 224 + createProps({ md: "# Test", h: -100, color: "#000" }), 225 + "shape1", 226 + ); 227 + 228 + page.shapeIds = ["shape1"]; 229 + doc.pages = { page1: page }; 230 + doc.shapes = { shape1: shape }; 231 + 232 + const result = validateDoc(doc); 233 + 234 + expect(result.ok).toBe(false); 235 + if (!result.ok) { 236 + expect(result.errors).toContain("Markdown shape 'shape1' has invalid height"); 237 + } 238 + }); 239 + 240 + it("should accept markdown with undefined height (auto-computed)", () => { 241 + const doc = Document.create(); 242 + const page = PageRecord.create("Page 1", "page1"); 243 + const shape = ShapeRecord.createMarkdown( 244 + "page1", 245 + 0, 246 + 0, 247 + createProps({ md: "# Test", h: undefined, color: "#000" }), 248 + "shape1", 249 + ); 250 + 251 + page.shapeIds = ["shape1"]; 252 + doc.pages = { page1: page }; 253 + doc.shapes = { shape1: shape }; 254 + 255 + const result = validateDoc(doc); 256 + 257 + expect(result.ok).toBe(true); 258 + }); 259 + }); 260 + 261 + describe("JSON serialization", () => { 262 + it("should round-trip markdown shape", () => { 263 + const doc = Document.create(); 264 + const page = PageRecord.create("Page 1", "page1"); 265 + const shape = ShapeRecord.createMarkdown( 266 + "page1", 267 + 10, 268 + 20, 269 + createProps({ 270 + md: "# Hello World\n\nThis is a **markdown** block.", 271 + color: "#1f2933", 272 + bg: "#ffffff", 273 + border: "#e0e0e0", 274 + }), 275 + "shape1", 276 + ); 277 + 278 + page.shapeIds = ["shape1"]; 279 + doc.pages = { page1: page }; 280 + doc.shapes = { shape1: shape }; 281 + 282 + const json = JSON.stringify(doc); 283 + const parsed = JSON.parse(json); 284 + 285 + expect(parsed).toEqual(doc); 286 + expect(validateDoc(parsed).ok).toBe(true); 287 + }); 288 + 289 + it("should round-trip markdown shape with complex markdown content", () => { 290 + const doc = Document.create(); 291 + const page = PageRecord.create("Page 1", "page1"); 292 + const markdown = `# Markdown Test 293 + 294 + ## Features 295 + 296 + - **Bold text** 297 + - *Italic text* 298 + - \`code\` 299 + 300 + ### Code Block 301 + 302 + \`\`\`javascript 303 + const hello = "world"; 304 + \`\`\` 305 + 306 + 1. Ordered 307 + 2. List 308 + 3. Items`; 309 + 310 + const shape = ShapeRecord.createMarkdown("page1", 0, 0, { 311 + md: markdown, 312 + w: 400, 313 + h: 500, 314 + fontSize: 14, 315 + fontFamily: "system-ui", 316 + color: "#000000", 317 + }, "shape1"); 318 + 319 + page.shapeIds = ["shape1"]; 320 + doc.pages = { page1: page }; 321 + doc.shapes = { shape1: shape }; 322 + 323 + const json = JSON.stringify(doc); 324 + const parsed = JSON.parse(json); 325 + 326 + expect(parsed).toEqual(doc); 327 + expect(validateDoc(parsed).ok).toBe(true); 328 + }); 329 + 330 + it("should round-trip document with markdown and other shapes", () => { 331 + const doc = Document.create(); 332 + const page = PageRecord.create("Page 1", "page1"); 333 + 334 + const rect = ShapeRecord.createRect( 335 + "page1", 336 + 0, 337 + 0, 338 + { w: 100, h: 50, fill: "#fff", stroke: "#000", radius: 5 }, 339 + "shape1", 340 + ); 341 + 342 + const markdown = ShapeRecord.createMarkdown("page1", 150, 100, { 343 + md: "# Markdown\n\nNext to a rectangle", 344 + w: 300, 345 + h: 200, 346 + fontSize: 16, 347 + fontFamily: "sans-serif", 348 + color: "#000", 349 + }, "shape2"); 350 + 351 + const text = ShapeRecord.createText("page1", 500, 200, { 352 + text: "Plain text", 353 + fontSize: 18, 354 + fontFamily: "Arial", 355 + color: "#333", 356 + }, "shape3"); 357 + 358 + page.shapeIds = ["shape1", "shape2", "shape3"]; 359 + doc.pages = { page1: page }; 360 + doc.shapes = { shape1: rect, shape2: markdown, shape3: text }; 361 + 362 + const json = JSON.stringify(doc); 363 + const parsed = JSON.parse(json); 364 + 365 + expect(parsed).toEqual(doc); 366 + expect(validateDoc(parsed).ok).toBe(true); 367 + }); 368 + }); 369 + 370 + describe("integration with EditorState", () => { 371 + it("should work in EditorState with markdown shapes", () => { 372 + const state = EditorStateOps.create(); 373 + const page = PageRecord.create("Test Page", "page1"); 374 + const markdown = ShapeRecord.createMarkdown( 375 + "page1", 376 + 0, 377 + 0, 378 + createProps({ md: "# Test", color: "#000" }), 379 + "shape1", 380 + ); 381 + 382 + page.shapeIds = ["shape1"]; 383 + state.doc.pages = { page1: page }; 384 + state.doc.shapes = { shape1: markdown }; 385 + state.ui.currentPageId = "page1"; 386 + 387 + const cloned = EditorStateOps.clone(state); 388 + 389 + expect(cloned).toEqual(state); 390 + expect(cloned).not.toBe(state); 391 + expect(cloned.doc.shapes.shape1).not.toBe(state.doc.shapes.shape1); 392 + }); 393 + }); 394 + });
+8 -1
packages/renderer/package.json
··· 33 33 "typescript-eslint": "^8.50.1", 34 34 "vitest": "^4.0.16" 35 35 }, 36 - "dependencies": { "inkfinite-core": "workspace:*" } 36 + "dependencies": { 37 + "inkfinite-core": "workspace:*", 38 + "rehype-stringify": "^10.0.1", 39 + "remark-gfm": "^4.0.1", 40 + "remark-parse": "^11.0.0", 41 + "remark-rehype": "^11.1.2", 42 + "unified": "^11.0.5" 43 + } 37 44 }
+222 -1
packages/renderer/src/index.ts
··· 5 5 EditorState, 6 6 EllipseShape, 7 7 LineShape, 8 + MarkdownShape, 8 9 RectShape, 9 10 ShapeRecord, 10 11 Store, ··· 370 371 drawText(context, shape); 371 372 break; 372 373 } 374 + case "markdown": { 375 + drawMarkdown(context, shape); 376 + break; 377 + } 373 378 case "stroke": { 374 379 drawStroke(context, shape); 375 380 break; ··· 604 609 } 605 610 606 611 /** 612 + * Parse and render markdown to canvas 613 + * 614 + * Renders markdown with basic formatting: 615 + * - Headings (h1-h6) with appropriate sizes 616 + * - Bold (**text** or __text__) 617 + * - Italic (*text* or _text_) 618 + * - Code (`code`) 619 + * - Paragraphs with line wrapping 620 + * - Lists (ordered and unordered) 621 + * - Code blocks (```) 622 + */ 623 + function drawMarkdown(context: CanvasRenderingContext2D, shape: MarkdownShape) { 624 + const { md, w, h, fontSize, fontFamily, color, bg, border } = shape.props; 625 + 626 + const width = w; 627 + const height = h ?? fontSize * 10; 628 + 629 + context.fillStyle = bg ?? "#ffffff"; 630 + context.fillRect(0, 0, width, height); 631 + 632 + if (border) { 633 + context.strokeStyle = border; 634 + context.lineWidth = 1; 635 + context.strokeRect(0, 0, width, height); 636 + } 637 + 638 + context.fillStyle = color; 639 + context.textBaseline = "top"; 640 + 641 + const padding = 8; 642 + let yOffset = padding; 643 + const lineHeight = fontSize * 1.4; 644 + 645 + const lines = md.split("\n"); 646 + 647 + for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { 648 + let line = lines[lineIndex]; 649 + 650 + if (yOffset + lineHeight > height - padding) break; 651 + 652 + let currentFontSize = fontSize; 653 + let currentStyle = "normal"; 654 + let currentWeight = "normal"; 655 + let prefix = ""; 656 + 657 + if (line.startsWith("```")) { 658 + context.fillStyle = "#f4f4f4"; 659 + const codeBlockLines = []; 660 + lineIndex++; 661 + while (lineIndex < lines.length && !lines[lineIndex].startsWith("```")) { 662 + codeBlockLines.push(lines[lineIndex]); 663 + lineIndex++; 664 + } 665 + 666 + const codeBlockHeight = codeBlockLines.length * lineHeight + padding * 2; 667 + if (yOffset + codeBlockHeight <= height - padding) { 668 + context.fillRect(padding, yOffset, width - padding * 2, codeBlockHeight); 669 + 670 + context.fillStyle = "#333"; 671 + context.font = `normal normal ${fontSize}px monospace`; 672 + 673 + for (const [index, codeLine] of codeBlockLines.entries()) { 674 + context.fillText(codeLine, padding + 4, yOffset + padding + index * lineHeight); 675 + } 676 + 677 + yOffset += codeBlockHeight + padding; 678 + } 679 + 680 + context.fillStyle = color; 681 + context.font = `${currentWeight} ${currentStyle} ${currentFontSize}px ${fontFamily}`; 682 + continue; 683 + } 684 + 685 + if (line.match(/^#{1,6}\s/)) { 686 + const match = line.match(/^(#{1,6})\s(.*)$/); 687 + if (match) { 688 + const level = match[1].length; 689 + line = match[2]; 690 + currentFontSize = fontSize * (2 - level * 0.15); 691 + currentWeight = "bold"; 692 + } 693 + } else if (line.match(/^[-*+]\s/)) { 694 + prefix = "• "; 695 + line = line.replace(/^[-*+]\s/, ""); 696 + } else if (line.match(/^\d+\.\s/)) { 697 + const match = line.match(/^(\d+)\.\s(.*)$/); 698 + if (match) { 699 + prefix = `${match[1]}. `; 700 + line = match[2]; 701 + } 702 + } 703 + 704 + line = prefix + line; 705 + 706 + line = line.replace(/`([^`]+)`/g, "$1"); 707 + 708 + context.font = `${currentWeight} ${currentStyle} ${currentFontSize}px ${fontFamily}`; 709 + 710 + const wrappedLines = wrapText(context, line, width - padding * 2); 711 + 712 + for (const wrappedLine of wrappedLines) { 713 + if (yOffset + currentFontSize * 1.4 > height - padding) break; 714 + 715 + const styledLine = wrappedLine; 716 + let xOffset = padding; 717 + 718 + const segments = parseInlineStyles(styledLine); 719 + 720 + for (const segment of segments) { 721 + const { text: segmentText, bold, italic, code } = segment; 722 + 723 + if (code) { 724 + context.fillStyle = "#f4f4f4"; 725 + const metrics = context.measureText(segmentText); 726 + context.fillRect(xOffset, yOffset, metrics.width + 4, currentFontSize * 1.2); 727 + context.fillStyle = "#333"; 728 + context.font = `normal normal ${currentFontSize * 0.9}px monospace`; 729 + context.fillText(segmentText, xOffset + 2, yOffset); 730 + xOffset += metrics.width + 4; 731 + context.fillStyle = color; 732 + context.font = `${currentWeight} ${currentStyle} ${currentFontSize}px ${fontFamily}`; 733 + } else { 734 + const weight = bold ? "bold" : currentWeight; 735 + const style = italic ? "italic" : currentStyle; 736 + context.font = `${weight} ${style} ${currentFontSize}px ${fontFamily}`; 737 + context.fillText(segmentText, xOffset, yOffset); 738 + const metrics = context.measureText(segmentText); 739 + xOffset += metrics.width; 740 + context.font = `${currentWeight} ${currentStyle} ${currentFontSize}px ${fontFamily}`; 741 + } 742 + } 743 + 744 + yOffset += currentFontSize * 1.4; 745 + } 746 + } 747 + } 748 + 749 + /** 750 + * Parse inline markdown styles (bold, italic, code) into segments 751 + */ 752 + function parseInlineStyles(text: string): Array<{ text: string; bold: boolean; italic: boolean; code: boolean }> { 753 + const segments: Array<{ text: string; bold: boolean; italic: boolean; code: boolean }> = []; 754 + 755 + const codeRegex = /`([^`]+)`/g; 756 + const parts = []; 757 + let lastIndex = 0; 758 + let match; 759 + 760 + while ((match = codeRegex.exec(text)) !== null) { 761 + if (match.index > lastIndex) { 762 + parts.push({ text: text.slice(lastIndex, match.index), code: false }); 763 + } 764 + parts.push({ text: match[1], code: true }); 765 + lastIndex = codeRegex.lastIndex; 766 + } 767 + 768 + if (lastIndex < text.length) { 769 + parts.push({ text: text.slice(lastIndex), code: false }); 770 + } 771 + 772 + for (const part of parts) { 773 + if (part.code) { 774 + segments.push({ text: part.text, bold: false, italic: false, code: true }); 775 + } else { 776 + const boldItalicRegex = /(\*\*\*|___)([^*_]+)(\*\*\*|___)|(\*\*|__)([^*_]+)(\*\*|__)|(\*|_)([^*_]+)(\*|_)/g; 777 + let lastPartIndex = 0; 778 + let partMatch; 779 + 780 + while ((partMatch = boldItalicRegex.exec(part.text)) !== null) { 781 + if (partMatch.index > lastPartIndex) { 782 + segments.push({ 783 + text: part.text.slice(lastPartIndex, partMatch.index), 784 + bold: false, 785 + italic: false, 786 + code: false, 787 + }); 788 + } 789 + 790 + if (partMatch[1]) { 791 + segments.push({ text: partMatch[2], bold: true, italic: true, code: false }); 792 + } else if (partMatch[4]) { 793 + segments.push({ text: partMatch[5], bold: true, italic: false, code: false }); 794 + } else if (partMatch[7]) { 795 + segments.push({ text: partMatch[8], bold: false, italic: true, code: false }); 796 + } 797 + 798 + lastPartIndex = boldItalicRegex.lastIndex; 799 + } 800 + 801 + if (lastPartIndex < part.text.length) { 802 + segments.push({ text: part.text.slice(lastPartIndex), bold: false, italic: false, code: false }); 803 + } 804 + 805 + if (segments.length === 0 || lastPartIndex === 0) { 806 + if (segments.length === 0) { 807 + segments.push({ text: part.text, bold: false, italic: false, code: false }); 808 + } 809 + } 810 + } 811 + } 812 + 813 + if (segments.length === 0) { 814 + segments.push({ text, bold: false, italic: false, code: false }); 815 + } 816 + 817 + return segments; 818 + } 819 + 820 + /** 607 821 * Draw a stroke shape (freehand drawing) 608 822 */ 609 823 function drawStroke(context: CanvasRenderingContext2D, shape: StrokeShape) { ··· 732 946 context.strokeRect(0, 0, width, height); 733 947 break; 734 948 } 949 + case "markdown": { 950 + const { w, h, fontSize } = shape.props; 951 + const width = w; 952 + const height = h ?? fontSize * 10; 953 + context.strokeRect(0, 0, width, height); 954 + break; 955 + } 735 956 case "stroke": { 736 957 const { points, brush } = shape.props; 737 958 if (points.length >= 2) { ··· 824 1045 825 1046 function getHandlesForShape(state: EditorState, shape: ShapeRecord): HandleVisual[] { 826 1047 const handles: HandleVisual[] = []; 827 - if (shape.type === "rect" || shape.type === "ellipse" || shape.type === "text") { 1048 + if (shape.type === "rect" || shape.type === "ellipse" || shape.type === "text" || shape.type === "markdown") { 828 1049 const bounds = shapeBounds(shape); 829 1050 const minX = bounds.min.x; 830 1051 const maxX = bounds.max.x;
+737
pnpm-lock.yaml
··· 166 166 inkfinite-core: 167 167 specifier: workspace:* 168 168 version: link:../core 169 + rehype-stringify: 170 + specifier: ^10.0.1 171 + version: 10.0.1 172 + remark-gfm: 173 + specifier: ^4.0.1 174 + version: 4.0.1 175 + remark-parse: 176 + specifier: ^11.0.0 177 + version: 11.0.0 178 + remark-rehype: 179 + specifier: ^11.1.2 180 + version: 11.1.2 181 + unified: 182 + specifier: ^11.0.5 183 + version: 11.0.5 169 184 devDependencies: 170 185 '@eslint/js': 171 186 specifier: ^9.39.2 ··· 896 911 '@types/cookie@0.6.0': 897 912 resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} 898 913 914 + '@types/debug@4.1.12': 915 + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} 916 + 899 917 '@types/deep-eql@4.0.2': 900 918 resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} 901 919 902 920 '@types/estree@1.0.8': 903 921 resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 904 922 923 + '@types/hast@3.0.4': 924 + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} 925 + 905 926 '@types/jsdom@27.0.0': 906 927 resolution: {integrity: sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==} 907 928 908 929 '@types/json-schema@7.0.15': 909 930 resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 910 931 932 + '@types/mdast@4.0.4': 933 + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} 934 + 935 + '@types/ms@2.1.0': 936 + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} 937 + 911 938 '@types/node@24.10.4': 912 939 resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==} 913 940 ··· 916 943 917 944 '@types/tough-cookie@4.0.5': 918 945 resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} 946 + 947 + '@types/unist@3.0.3': 948 + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} 919 949 920 950 '@typescript-eslint/eslint-plugin@8.50.0': 921 951 resolution: {integrity: sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==} ··· 1035 1065 resolution: {integrity: sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==} 1036 1066 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1037 1067 1068 + '@ungap/structured-clone@1.3.0': 1069 + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} 1070 + 1038 1071 '@vitest/browser-playwright@4.0.16': 1039 1072 resolution: {integrity: sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==} 1040 1073 peerDependencies: ··· 1134 1167 resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} 1135 1168 engines: {node: '>= 0.4'} 1136 1169 1170 + bail@2.0.2: 1171 + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} 1172 + 1137 1173 balanced-match@1.0.2: 1138 1174 resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 1139 1175 ··· 1170 1206 resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 1171 1207 engines: {node: '>=6'} 1172 1208 1209 + ccount@2.0.1: 1210 + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} 1211 + 1173 1212 chai@6.2.1: 1174 1213 resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} 1175 1214 engines: {node: '>=18'} ··· 1178 1217 resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 1179 1218 engines: {node: '>=10'} 1180 1219 1220 + character-entities-html4@2.1.0: 1221 + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} 1222 + 1223 + character-entities-legacy@3.0.0: 1224 + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} 1225 + 1226 + character-entities@2.0.2: 1227 + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} 1228 + 1181 1229 chokidar@4.0.3: 1182 1230 resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} 1183 1231 engines: {node: '>= 14.16.0'} ··· 1199 1247 1200 1248 color-name@1.1.4: 1201 1249 resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 1250 + 1251 + comma-separated-tokens@2.0.3: 1252 + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} 1202 1253 1203 1254 concat-map@0.0.1: 1204 1255 resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} ··· 1247 1298 decimal.js@10.6.0: 1248 1299 resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} 1249 1300 1301 + decode-named-character-reference@1.2.0: 1302 + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} 1303 + 1250 1304 deep-is@0.1.4: 1251 1305 resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 1252 1306 ··· 1257 1311 defu@6.1.4: 1258 1312 resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} 1259 1313 1314 + dequal@2.0.3: 1315 + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} 1316 + engines: {node: '>=6'} 1317 + 1260 1318 destr@2.0.5: 1261 1319 resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} 1262 1320 1263 1321 devalue@5.6.1: 1264 1322 resolution: {integrity: sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==} 1265 1323 1324 + devlop@1.1.0: 1325 + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} 1326 + 1266 1327 dexie@4.2.1: 1267 1328 resolution: {integrity: sha512-Ckej0NS6jxQ4Po3OrSQBFddayRhTCic2DoCAG5zacOfOVB9P2Q5Xc5uL/nVa7ZVs+HdMnvUPzLFCB/JwpB6Csg==} 1268 1329 ··· 1306 1367 escape-string-regexp@4.0.0: 1307 1368 resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 1308 1369 engines: {node: '>=10'} 1370 + 1371 + escape-string-regexp@5.0.0: 1372 + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} 1373 + engines: {node: '>=12'} 1309 1374 1310 1375 eslint-config-prettier@10.1.8: 1311 1376 resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} ··· 1381 1446 exsolve@1.0.8: 1382 1447 resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} 1383 1448 1449 + extend@3.0.2: 1450 + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} 1451 + 1384 1452 fake-indexeddb@6.2.5: 1385 1453 resolution: {integrity: sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==} 1386 1454 engines: {node: '>=18'} ··· 1451 1519 resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 1452 1520 engines: {node: '>=8'} 1453 1521 1522 + hast-util-to-html@9.0.5: 1523 + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} 1524 + 1525 + hast-util-whitespace@3.0.0: 1526 + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} 1527 + 1454 1528 hookable@5.5.3: 1455 1529 resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} 1456 1530 ··· 1460 1534 1461 1535 html-escaper@2.0.2: 1462 1536 resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} 1537 + 1538 + html-void-elements@3.0.0: 1539 + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} 1463 1540 1464 1541 http-proxy-agent@7.0.2: 1465 1542 resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} ··· 1500 1577 is-glob@4.0.3: 1501 1578 resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 1502 1579 engines: {node: '>=0.10.0'} 1580 + 1581 + is-plain-obj@4.1.0: 1582 + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} 1583 + engines: {node: '>=12'} 1503 1584 1504 1585 is-potential-custom-element-name@1.0.1: 1505 1586 resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} ··· 1591 1672 lodash.merge@4.6.2: 1592 1673 resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 1593 1674 1675 + longest-streak@3.1.0: 1676 + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} 1677 + 1594 1678 lru-cache@11.2.4: 1595 1679 resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} 1596 1680 engines: {node: 20 || >=22} ··· 1605 1689 resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} 1606 1690 engines: {node: '>=10'} 1607 1691 1692 + markdown-table@3.0.4: 1693 + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} 1694 + 1695 + mdast-util-find-and-replace@3.0.2: 1696 + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} 1697 + 1698 + mdast-util-from-markdown@2.0.2: 1699 + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} 1700 + 1701 + mdast-util-gfm-autolink-literal@2.0.1: 1702 + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} 1703 + 1704 + mdast-util-gfm-footnote@2.1.0: 1705 + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} 1706 + 1707 + mdast-util-gfm-strikethrough@2.0.0: 1708 + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} 1709 + 1710 + mdast-util-gfm-table@2.0.0: 1711 + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} 1712 + 1713 + mdast-util-gfm-task-list-item@2.0.0: 1714 + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} 1715 + 1716 + mdast-util-gfm@3.1.0: 1717 + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} 1718 + 1719 + mdast-util-phrasing@4.1.0: 1720 + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} 1721 + 1722 + mdast-util-to-hast@13.2.1: 1723 + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} 1724 + 1725 + mdast-util-to-markdown@2.1.2: 1726 + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} 1727 + 1728 + mdast-util-to-string@4.0.0: 1729 + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} 1730 + 1608 1731 mdn-data@2.12.2: 1609 1732 resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} 1610 1733 1734 + micromark-core-commonmark@2.0.3: 1735 + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} 1736 + 1737 + micromark-extension-gfm-autolink-literal@2.1.0: 1738 + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} 1739 + 1740 + micromark-extension-gfm-footnote@2.1.0: 1741 + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} 1742 + 1743 + micromark-extension-gfm-strikethrough@2.1.0: 1744 + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} 1745 + 1746 + micromark-extension-gfm-table@2.1.1: 1747 + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} 1748 + 1749 + micromark-extension-gfm-tagfilter@2.0.0: 1750 + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} 1751 + 1752 + micromark-extension-gfm-task-list-item@2.1.0: 1753 + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} 1754 + 1755 + micromark-extension-gfm@3.0.0: 1756 + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} 1757 + 1758 + micromark-factory-destination@2.0.1: 1759 + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} 1760 + 1761 + micromark-factory-label@2.0.1: 1762 + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} 1763 + 1764 + micromark-factory-space@2.0.1: 1765 + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} 1766 + 1767 + micromark-factory-title@2.0.1: 1768 + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} 1769 + 1770 + micromark-factory-whitespace@2.0.1: 1771 + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} 1772 + 1773 + micromark-util-character@2.1.1: 1774 + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} 1775 + 1776 + micromark-util-chunked@2.0.1: 1777 + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} 1778 + 1779 + micromark-util-classify-character@2.0.1: 1780 + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} 1781 + 1782 + micromark-util-combine-extensions@2.0.1: 1783 + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} 1784 + 1785 + micromark-util-decode-numeric-character-reference@2.0.2: 1786 + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} 1787 + 1788 + micromark-util-decode-string@2.0.1: 1789 + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} 1790 + 1791 + micromark-util-encode@2.0.1: 1792 + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} 1793 + 1794 + micromark-util-html-tag-name@2.0.1: 1795 + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} 1796 + 1797 + micromark-util-normalize-identifier@2.0.1: 1798 + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} 1799 + 1800 + micromark-util-resolve-all@2.0.1: 1801 + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} 1802 + 1803 + micromark-util-sanitize-uri@2.0.1: 1804 + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} 1805 + 1806 + micromark-util-subtokenize@2.1.0: 1807 + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} 1808 + 1809 + micromark-util-symbol@2.0.1: 1810 + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} 1811 + 1812 + micromark-util-types@2.0.2: 1813 + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} 1814 + 1815 + micromark@4.0.2: 1816 + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} 1817 + 1611 1818 minimatch@3.1.2: 1612 1819 resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 1613 1820 ··· 1765 1972 engines: {node: '>=14'} 1766 1973 hasBin: true 1767 1974 1975 + property-information@7.1.0: 1976 + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} 1977 + 1768 1978 punycode@2.3.1: 1769 1979 resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 1770 1980 engines: {node: '>=6'} ··· 1782 1992 readdirp@5.0.0: 1783 1993 resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} 1784 1994 engines: {node: '>= 20.19.0'} 1995 + 1996 + rehype-stringify@10.0.1: 1997 + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} 1998 + 1999 + remark-gfm@4.0.1: 2000 + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} 2001 + 2002 + remark-parse@11.0.0: 2003 + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} 2004 + 2005 + remark-rehype@11.1.2: 2006 + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} 2007 + 2008 + remark-stringify@11.0.0: 2009 + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} 1785 2010 1786 2011 require-from-string@2.0.2: 1787 2012 resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} ··· 1864 2089 resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 1865 2090 engines: {node: '>=0.10.0'} 1866 2091 2092 + space-separated-tokens@2.0.2: 2093 + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} 2094 + 1867 2095 stackback@0.0.2: 1868 2096 resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} 1869 2097 1870 2098 std-env@3.10.0: 1871 2099 resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} 2100 + 2101 + stringify-entities@4.0.4: 2102 + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} 1872 2103 1873 2104 strip-json-comments@3.1.1: 1874 2105 resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} ··· 1940 2171 resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} 1941 2172 hasBin: true 1942 2173 2174 + trim-lines@3.0.1: 2175 + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} 2176 + 2177 + trough@2.2.0: 2178 + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} 2179 + 1943 2180 ts-api-utils@2.1.0: 1944 2181 resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} 1945 2182 engines: {node: '>=18.12'} ··· 2003 2240 undici-types@7.16.0: 2004 2241 resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} 2005 2242 2243 + unified@11.0.5: 2244 + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} 2245 + 2246 + unist-util-is@6.0.1: 2247 + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} 2248 + 2249 + unist-util-position@5.0.0: 2250 + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} 2251 + 2252 + unist-util-stringify-position@4.0.0: 2253 + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} 2254 + 2255 + unist-util-visit-parents@6.0.2: 2256 + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} 2257 + 2258 + unist-util-visit@5.0.0: 2259 + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} 2260 + 2006 2261 unrun@0.2.20: 2007 2262 resolution: {integrity: sha512-YhobStTk93HYRN/4iBs3q3/sd7knvju1XrzwwrVVfRujyTG1K88hGONIxCoJN0PWBuO+BX7fFiHH0sVDfE3MWw==} 2008 2263 engines: {node: '>=20.19.0'} ··· 2026 2281 uuid@13.0.0: 2027 2282 resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} 2028 2283 hasBin: true 2284 + 2285 + vfile-message@4.0.3: 2286 + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} 2287 + 2288 + vfile@6.0.3: 2289 + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} 2029 2290 2030 2291 vite-plugin-devtools-json@1.0.0: 2031 2292 resolution: {integrity: sha512-MobvwqX76Vqt/O4AbnNMNWoXWGrKUqZbphCUle/J2KXH82yKQiunOeKnz/nqEPosPsoWWPP9FtNuPBSYpiiwkw==} ··· 2188 2449 2189 2450 zimmerframe@1.1.4: 2190 2451 resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} 2452 + 2453 + zwitch@2.0.4: 2454 + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} 2191 2455 2192 2456 snapshots: 2193 2457 ··· 2706 2970 2707 2971 '@types/cookie@0.6.0': {} 2708 2972 2973 + '@types/debug@4.1.12': 2974 + dependencies: 2975 + '@types/ms': 2.1.0 2976 + 2709 2977 '@types/deep-eql@4.0.2': {} 2710 2978 2711 2979 '@types/estree@1.0.8': {} 2712 2980 2981 + '@types/hast@3.0.4': 2982 + dependencies: 2983 + '@types/unist': 3.0.3 2984 + 2713 2985 '@types/jsdom@27.0.0': 2714 2986 dependencies: 2715 2987 '@types/node': 25.0.3 ··· 2718 2990 2719 2991 '@types/json-schema@7.0.15': {} 2720 2992 2993 + '@types/mdast@4.0.4': 2994 + dependencies: 2995 + '@types/unist': 3.0.3 2996 + 2997 + '@types/ms@2.1.0': {} 2998 + 2721 2999 '@types/node@24.10.4': 2722 3000 dependencies: 2723 3001 undici-types: 7.16.0 ··· 2727 3005 undici-types: 7.16.0 2728 3006 2729 3007 '@types/tough-cookie@4.0.5': {} 3008 + 3009 + '@types/unist@3.0.3': {} 2730 3010 2731 3011 '@typescript-eslint/eslint-plugin@8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': 2732 3012 dependencies: ··· 2910 3190 '@typescript-eslint/types': 8.50.1 2911 3191 eslint-visitor-keys: 4.2.1 2912 3192 3193 + '@ungap/structured-clone@1.3.0': {} 3194 + 2913 3195 '@vitest/browser-playwright@4.0.16(playwright@1.57.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.16)': 2914 3196 dependencies: 2915 3197 '@vitest/browser': 4.0.16(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.16) ··· 3080 3362 3081 3363 axobject-query@4.1.0: {} 3082 3364 3365 + bail@2.0.2: {} 3366 + 3083 3367 balanced-match@1.0.2: {} 3084 3368 3085 3369 bidi-js@1.0.3: ··· 3134 3418 3135 3419 callsites@3.1.0: {} 3136 3420 3421 + ccount@2.0.1: {} 3422 + 3137 3423 chai@6.2.1: {} 3138 3424 3139 3425 chalk@4.1.2: ··· 3141 3427 ansi-styles: 4.3.0 3142 3428 supports-color: 7.2.0 3143 3429 3430 + character-entities-html4@2.1.0: {} 3431 + 3432 + character-entities-legacy@3.0.0: {} 3433 + 3434 + character-entities@2.0.2: {} 3435 + 3144 3436 chokidar@4.0.3: 3145 3437 dependencies: 3146 3438 readdirp: 4.1.2 ··· 3160 3452 color-name: 1.1.4 3161 3453 3162 3454 color-name@1.1.4: {} 3455 + 3456 + comma-separated-tokens@2.0.3: {} 3163 3457 3164 3458 concat-map@0.0.1: {} 3165 3459 ··· 3199 3493 3200 3494 decimal.js@10.6.0: {} 3201 3495 3496 + decode-named-character-reference@1.2.0: 3497 + dependencies: 3498 + character-entities: 2.0.2 3499 + 3202 3500 deep-is@0.1.4: {} 3203 3501 3204 3502 deepmerge@4.3.1: {} 3205 3503 3206 3504 defu@6.1.4: {} 3505 + 3506 + dequal@2.0.3: {} 3207 3507 3208 3508 destr@2.0.5: {} 3209 3509 3210 3510 devalue@5.6.1: {} 3211 3511 3512 + devlop@1.1.0: 3513 + dependencies: 3514 + dequal: 2.0.3 3515 + 3212 3516 dexie@4.2.1: {} 3213 3517 3214 3518 dotenv@17.2.3: {} ··· 3265 3569 escalade@3.2.0: {} 3266 3570 3267 3571 escape-string-regexp@4.0.0: {} 3572 + 3573 + escape-string-regexp@5.0.0: {} 3268 3574 3269 3575 eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)): 3270 3576 dependencies: ··· 3370 3676 3371 3677 exsolve@1.0.8: {} 3372 3678 3679 + extend@3.0.2: {} 3680 + 3373 3681 fake-indexeddb@6.2.5: {} 3374 3682 3375 3683 fast-deep-equal@3.1.3: {} ··· 3427 3735 3428 3736 has-flag@4.0.0: {} 3429 3737 3738 + hast-util-to-html@9.0.5: 3739 + dependencies: 3740 + '@types/hast': 3.0.4 3741 + '@types/unist': 3.0.3 3742 + ccount: 2.0.1 3743 + comma-separated-tokens: 2.0.3 3744 + hast-util-whitespace: 3.0.0 3745 + html-void-elements: 3.0.0 3746 + mdast-util-to-hast: 13.2.1 3747 + property-information: 7.1.0 3748 + space-separated-tokens: 2.0.2 3749 + stringify-entities: 4.0.4 3750 + zwitch: 2.0.4 3751 + 3752 + hast-util-whitespace@3.0.0: 3753 + dependencies: 3754 + '@types/hast': 3.0.4 3755 + 3430 3756 hookable@5.5.3: {} 3431 3757 3432 3758 html-encoding-sniffer@4.0.0: ··· 3434 3760 whatwg-encoding: 3.1.1 3435 3761 3436 3762 html-escaper@2.0.2: {} 3763 + 3764 + html-void-elements@3.0.0: {} 3437 3765 3438 3766 http-proxy-agent@7.0.2: 3439 3767 dependencies: ··· 3471 3799 is-glob@4.0.3: 3472 3800 dependencies: 3473 3801 is-extglob: 2.1.1 3802 + 3803 + is-plain-obj@4.1.0: {} 3474 3804 3475 3805 is-potential-custom-element-name@1.0.1: {} 3476 3806 ··· 3569 3899 3570 3900 lodash.merge@4.6.2: {} 3571 3901 3902 + longest-streak@3.1.0: {} 3903 + 3572 3904 lru-cache@11.2.4: {} 3573 3905 3574 3906 magic-string@0.30.21: ··· 3585 3917 dependencies: 3586 3918 semver: 7.7.3 3587 3919 3920 + markdown-table@3.0.4: {} 3921 + 3922 + mdast-util-find-and-replace@3.0.2: 3923 + dependencies: 3924 + '@types/mdast': 4.0.4 3925 + escape-string-regexp: 5.0.0 3926 + unist-util-is: 6.0.1 3927 + unist-util-visit-parents: 6.0.2 3928 + 3929 + mdast-util-from-markdown@2.0.2: 3930 + dependencies: 3931 + '@types/mdast': 4.0.4 3932 + '@types/unist': 3.0.3 3933 + decode-named-character-reference: 1.2.0 3934 + devlop: 1.1.0 3935 + mdast-util-to-string: 4.0.0 3936 + micromark: 4.0.2 3937 + micromark-util-decode-numeric-character-reference: 2.0.2 3938 + micromark-util-decode-string: 2.0.1 3939 + micromark-util-normalize-identifier: 2.0.1 3940 + micromark-util-symbol: 2.0.1 3941 + micromark-util-types: 2.0.2 3942 + unist-util-stringify-position: 4.0.0 3943 + transitivePeerDependencies: 3944 + - supports-color 3945 + 3946 + mdast-util-gfm-autolink-literal@2.0.1: 3947 + dependencies: 3948 + '@types/mdast': 4.0.4 3949 + ccount: 2.0.1 3950 + devlop: 1.1.0 3951 + mdast-util-find-and-replace: 3.0.2 3952 + micromark-util-character: 2.1.1 3953 + 3954 + mdast-util-gfm-footnote@2.1.0: 3955 + dependencies: 3956 + '@types/mdast': 4.0.4 3957 + devlop: 1.1.0 3958 + mdast-util-from-markdown: 2.0.2 3959 + mdast-util-to-markdown: 2.1.2 3960 + micromark-util-normalize-identifier: 2.0.1 3961 + transitivePeerDependencies: 3962 + - supports-color 3963 + 3964 + mdast-util-gfm-strikethrough@2.0.0: 3965 + dependencies: 3966 + '@types/mdast': 4.0.4 3967 + mdast-util-from-markdown: 2.0.2 3968 + mdast-util-to-markdown: 2.1.2 3969 + transitivePeerDependencies: 3970 + - supports-color 3971 + 3972 + mdast-util-gfm-table@2.0.0: 3973 + dependencies: 3974 + '@types/mdast': 4.0.4 3975 + devlop: 1.1.0 3976 + markdown-table: 3.0.4 3977 + mdast-util-from-markdown: 2.0.2 3978 + mdast-util-to-markdown: 2.1.2 3979 + transitivePeerDependencies: 3980 + - supports-color 3981 + 3982 + mdast-util-gfm-task-list-item@2.0.0: 3983 + dependencies: 3984 + '@types/mdast': 4.0.4 3985 + devlop: 1.1.0 3986 + mdast-util-from-markdown: 2.0.2 3987 + mdast-util-to-markdown: 2.1.2 3988 + transitivePeerDependencies: 3989 + - supports-color 3990 + 3991 + mdast-util-gfm@3.1.0: 3992 + dependencies: 3993 + mdast-util-from-markdown: 2.0.2 3994 + mdast-util-gfm-autolink-literal: 2.0.1 3995 + mdast-util-gfm-footnote: 2.1.0 3996 + mdast-util-gfm-strikethrough: 2.0.0 3997 + mdast-util-gfm-table: 2.0.0 3998 + mdast-util-gfm-task-list-item: 2.0.0 3999 + mdast-util-to-markdown: 2.1.2 4000 + transitivePeerDependencies: 4001 + - supports-color 4002 + 4003 + mdast-util-phrasing@4.1.0: 4004 + dependencies: 4005 + '@types/mdast': 4.0.4 4006 + unist-util-is: 6.0.1 4007 + 4008 + mdast-util-to-hast@13.2.1: 4009 + dependencies: 4010 + '@types/hast': 3.0.4 4011 + '@types/mdast': 4.0.4 4012 + '@ungap/structured-clone': 1.3.0 4013 + devlop: 1.1.0 4014 + micromark-util-sanitize-uri: 2.0.1 4015 + trim-lines: 3.0.1 4016 + unist-util-position: 5.0.0 4017 + unist-util-visit: 5.0.0 4018 + vfile: 6.0.3 4019 + 4020 + mdast-util-to-markdown@2.1.2: 4021 + dependencies: 4022 + '@types/mdast': 4.0.4 4023 + '@types/unist': 3.0.3 4024 + longest-streak: 3.1.0 4025 + mdast-util-phrasing: 4.1.0 4026 + mdast-util-to-string: 4.0.0 4027 + micromark-util-classify-character: 2.0.1 4028 + micromark-util-decode-string: 2.0.1 4029 + unist-util-visit: 5.0.0 4030 + zwitch: 2.0.4 4031 + 4032 + mdast-util-to-string@4.0.0: 4033 + dependencies: 4034 + '@types/mdast': 4.0.4 4035 + 3588 4036 mdn-data@2.12.2: {} 3589 4037 4038 + micromark-core-commonmark@2.0.3: 4039 + dependencies: 4040 + decode-named-character-reference: 1.2.0 4041 + devlop: 1.1.0 4042 + micromark-factory-destination: 2.0.1 4043 + micromark-factory-label: 2.0.1 4044 + micromark-factory-space: 2.0.1 4045 + micromark-factory-title: 2.0.1 4046 + micromark-factory-whitespace: 2.0.1 4047 + micromark-util-character: 2.1.1 4048 + micromark-util-chunked: 2.0.1 4049 + micromark-util-classify-character: 2.0.1 4050 + micromark-util-html-tag-name: 2.0.1 4051 + micromark-util-normalize-identifier: 2.0.1 4052 + micromark-util-resolve-all: 2.0.1 4053 + micromark-util-subtokenize: 2.1.0 4054 + micromark-util-symbol: 2.0.1 4055 + micromark-util-types: 2.0.2 4056 + 4057 + micromark-extension-gfm-autolink-literal@2.1.0: 4058 + dependencies: 4059 + micromark-util-character: 2.1.1 4060 + micromark-util-sanitize-uri: 2.0.1 4061 + micromark-util-symbol: 2.0.1 4062 + micromark-util-types: 2.0.2 4063 + 4064 + micromark-extension-gfm-footnote@2.1.0: 4065 + dependencies: 4066 + devlop: 1.1.0 4067 + micromark-core-commonmark: 2.0.3 4068 + micromark-factory-space: 2.0.1 4069 + micromark-util-character: 2.1.1 4070 + micromark-util-normalize-identifier: 2.0.1 4071 + micromark-util-sanitize-uri: 2.0.1 4072 + micromark-util-symbol: 2.0.1 4073 + micromark-util-types: 2.0.2 4074 + 4075 + micromark-extension-gfm-strikethrough@2.1.0: 4076 + dependencies: 4077 + devlop: 1.1.0 4078 + micromark-util-chunked: 2.0.1 4079 + micromark-util-classify-character: 2.0.1 4080 + micromark-util-resolve-all: 2.0.1 4081 + micromark-util-symbol: 2.0.1 4082 + micromark-util-types: 2.0.2 4083 + 4084 + micromark-extension-gfm-table@2.1.1: 4085 + dependencies: 4086 + devlop: 1.1.0 4087 + micromark-factory-space: 2.0.1 4088 + micromark-util-character: 2.1.1 4089 + micromark-util-symbol: 2.0.1 4090 + micromark-util-types: 2.0.2 4091 + 4092 + micromark-extension-gfm-tagfilter@2.0.0: 4093 + dependencies: 4094 + micromark-util-types: 2.0.2 4095 + 4096 + micromark-extension-gfm-task-list-item@2.1.0: 4097 + dependencies: 4098 + devlop: 1.1.0 4099 + micromark-factory-space: 2.0.1 4100 + micromark-util-character: 2.1.1 4101 + micromark-util-symbol: 2.0.1 4102 + micromark-util-types: 2.0.2 4103 + 4104 + micromark-extension-gfm@3.0.0: 4105 + dependencies: 4106 + micromark-extension-gfm-autolink-literal: 2.1.0 4107 + micromark-extension-gfm-footnote: 2.1.0 4108 + micromark-extension-gfm-strikethrough: 2.1.0 4109 + micromark-extension-gfm-table: 2.1.1 4110 + micromark-extension-gfm-tagfilter: 2.0.0 4111 + micromark-extension-gfm-task-list-item: 2.1.0 4112 + micromark-util-combine-extensions: 2.0.1 4113 + micromark-util-types: 2.0.2 4114 + 4115 + micromark-factory-destination@2.0.1: 4116 + dependencies: 4117 + micromark-util-character: 2.1.1 4118 + micromark-util-symbol: 2.0.1 4119 + micromark-util-types: 2.0.2 4120 + 4121 + micromark-factory-label@2.0.1: 4122 + dependencies: 4123 + devlop: 1.1.0 4124 + micromark-util-character: 2.1.1 4125 + micromark-util-symbol: 2.0.1 4126 + micromark-util-types: 2.0.2 4127 + 4128 + micromark-factory-space@2.0.1: 4129 + dependencies: 4130 + micromark-util-character: 2.1.1 4131 + micromark-util-types: 2.0.2 4132 + 4133 + micromark-factory-title@2.0.1: 4134 + dependencies: 4135 + micromark-factory-space: 2.0.1 4136 + micromark-util-character: 2.1.1 4137 + micromark-util-symbol: 2.0.1 4138 + micromark-util-types: 2.0.2 4139 + 4140 + micromark-factory-whitespace@2.0.1: 4141 + dependencies: 4142 + micromark-factory-space: 2.0.1 4143 + micromark-util-character: 2.1.1 4144 + micromark-util-symbol: 2.0.1 4145 + micromark-util-types: 2.0.2 4146 + 4147 + micromark-util-character@2.1.1: 4148 + dependencies: 4149 + micromark-util-symbol: 2.0.1 4150 + micromark-util-types: 2.0.2 4151 + 4152 + micromark-util-chunked@2.0.1: 4153 + dependencies: 4154 + micromark-util-symbol: 2.0.1 4155 + 4156 + micromark-util-classify-character@2.0.1: 4157 + dependencies: 4158 + micromark-util-character: 2.1.1 4159 + micromark-util-symbol: 2.0.1 4160 + micromark-util-types: 2.0.2 4161 + 4162 + micromark-util-combine-extensions@2.0.1: 4163 + dependencies: 4164 + micromark-util-chunked: 2.0.1 4165 + micromark-util-types: 2.0.2 4166 + 4167 + micromark-util-decode-numeric-character-reference@2.0.2: 4168 + dependencies: 4169 + micromark-util-symbol: 2.0.1 4170 + 4171 + micromark-util-decode-string@2.0.1: 4172 + dependencies: 4173 + decode-named-character-reference: 1.2.0 4174 + micromark-util-character: 2.1.1 4175 + micromark-util-decode-numeric-character-reference: 2.0.2 4176 + micromark-util-symbol: 2.0.1 4177 + 4178 + micromark-util-encode@2.0.1: {} 4179 + 4180 + micromark-util-html-tag-name@2.0.1: {} 4181 + 4182 + micromark-util-normalize-identifier@2.0.1: 4183 + dependencies: 4184 + micromark-util-symbol: 2.0.1 4185 + 4186 + micromark-util-resolve-all@2.0.1: 4187 + dependencies: 4188 + micromark-util-types: 2.0.2 4189 + 4190 + micromark-util-sanitize-uri@2.0.1: 4191 + dependencies: 4192 + micromark-util-character: 2.1.1 4193 + micromark-util-encode: 2.0.1 4194 + micromark-util-symbol: 2.0.1 4195 + 4196 + micromark-util-subtokenize@2.1.0: 4197 + dependencies: 4198 + devlop: 1.1.0 4199 + micromark-util-chunked: 2.0.1 4200 + micromark-util-symbol: 2.0.1 4201 + micromark-util-types: 2.0.2 4202 + 4203 + micromark-util-symbol@2.0.1: {} 4204 + 4205 + micromark-util-types@2.0.2: {} 4206 + 4207 + micromark@4.0.2: 4208 + dependencies: 4209 + '@types/debug': 4.1.12 4210 + debug: 4.4.3 4211 + decode-named-character-reference: 1.2.0 4212 + devlop: 1.1.0 4213 + micromark-core-commonmark: 2.0.3 4214 + micromark-factory-space: 2.0.1 4215 + micromark-util-character: 2.1.1 4216 + micromark-util-chunked: 2.0.1 4217 + micromark-util-combine-extensions: 2.0.1 4218 + micromark-util-decode-numeric-character-reference: 2.0.2 4219 + micromark-util-encode: 2.0.1 4220 + micromark-util-normalize-identifier: 2.0.1 4221 + micromark-util-resolve-all: 2.0.1 4222 + micromark-util-sanitize-uri: 2.0.1 4223 + micromark-util-subtokenize: 2.1.0 4224 + micromark-util-symbol: 2.0.1 4225 + micromark-util-types: 2.0.2 4226 + transitivePeerDependencies: 4227 + - supports-color 4228 + 3590 4229 minimatch@3.1.2: 3591 4230 dependencies: 3592 4231 brace-expansion: 1.1.12 ··· 3719 4358 3720 4359 prettier@3.7.4: {} 3721 4360 4361 + property-information@7.1.0: {} 4362 + 3722 4363 punycode@2.3.1: {} 3723 4364 3724 4365 quansync@1.0.0: {} ··· 3732 4373 3733 4374 readdirp@5.0.0: {} 3734 4375 4376 + rehype-stringify@10.0.1: 4377 + dependencies: 4378 + '@types/hast': 3.0.4 4379 + hast-util-to-html: 9.0.5 4380 + unified: 11.0.5 4381 + 4382 + remark-gfm@4.0.1: 4383 + dependencies: 4384 + '@types/mdast': 4.0.4 4385 + mdast-util-gfm: 3.1.0 4386 + micromark-extension-gfm: 3.0.0 4387 + remark-parse: 11.0.0 4388 + remark-stringify: 11.0.0 4389 + unified: 11.0.5 4390 + transitivePeerDependencies: 4391 + - supports-color 4392 + 4393 + remark-parse@11.0.0: 4394 + dependencies: 4395 + '@types/mdast': 4.0.4 4396 + mdast-util-from-markdown: 2.0.2 4397 + micromark-util-types: 2.0.2 4398 + unified: 11.0.5 4399 + transitivePeerDependencies: 4400 + - supports-color 4401 + 4402 + remark-rehype@11.1.2: 4403 + dependencies: 4404 + '@types/hast': 3.0.4 4405 + '@types/mdast': 4.0.4 4406 + mdast-util-to-hast: 13.2.1 4407 + unified: 11.0.5 4408 + vfile: 6.0.3 4409 + 4410 + remark-stringify@11.0.0: 4411 + dependencies: 4412 + '@types/mdast': 4.0.4 4413 + mdast-util-to-markdown: 2.1.2 4414 + unified: 11.0.5 4415 + 3735 4416 require-from-string@2.0.2: {} 3736 4417 3737 4418 resolve-from@4.0.0: {} ··· 3835 4516 3836 4517 source-map-js@1.2.1: {} 3837 4518 4519 + space-separated-tokens@2.0.2: {} 4520 + 3838 4521 stackback@0.0.2: {} 3839 4522 3840 4523 std-env@3.10.0: {} 4524 + 4525 + stringify-entities@4.0.4: 4526 + dependencies: 4527 + character-entities-html4: 2.1.0 4528 + character-entities-legacy: 3.0.0 3841 4529 3842 4530 strip-json-comments@3.1.1: {} 3843 4531 ··· 3917 4605 3918 4606 tree-kill@1.2.2: {} 3919 4607 4608 + trim-lines@3.0.1: {} 4609 + 4610 + trough@2.2.0: {} 4611 + 3920 4612 ts-api-utils@2.1.0(typescript@5.9.3): 3921 4613 dependencies: 3922 4614 typescript: 5.9.3 ··· 3985 4677 3986 4678 undici-types@7.16.0: {} 3987 4679 4680 + unified@11.0.5: 4681 + dependencies: 4682 + '@types/unist': 3.0.3 4683 + bail: 2.0.2 4684 + devlop: 1.1.0 4685 + extend: 3.0.2 4686 + is-plain-obj: 4.1.0 4687 + trough: 2.2.0 4688 + vfile: 6.0.3 4689 + 4690 + unist-util-is@6.0.1: 4691 + dependencies: 4692 + '@types/unist': 3.0.3 4693 + 4694 + unist-util-position@5.0.0: 4695 + dependencies: 4696 + '@types/unist': 3.0.3 4697 + 4698 + unist-util-stringify-position@4.0.0: 4699 + dependencies: 4700 + '@types/unist': 3.0.3 4701 + 4702 + unist-util-visit-parents@6.0.2: 4703 + dependencies: 4704 + '@types/unist': 3.0.3 4705 + unist-util-is: 6.0.1 4706 + 4707 + unist-util-visit@5.0.0: 4708 + dependencies: 4709 + '@types/unist': 3.0.3 4710 + unist-util-is: 6.0.1 4711 + unist-util-visit-parents: 6.0.2 4712 + 3988 4713 unrun@0.2.20: 3989 4714 dependencies: 3990 4715 rolldown: 1.0.0-beta.55 ··· 3998 4723 uuid@11.1.0: {} 3999 4724 4000 4725 uuid@13.0.0: {} 4726 + 4727 + vfile-message@4.0.3: 4728 + dependencies: 4729 + '@types/unist': 3.0.3 4730 + unist-util-stringify-position: 4.0.0 4731 + 4732 + vfile@6.0.3: 4733 + dependencies: 4734 + '@types/unist': 3.0.3 4735 + vfile-message: 4.0.3 4001 4736 4002 4737 vite-plugin-devtools-json@1.0.0(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)): 4003 4738 dependencies: ··· 4160 4895 yocto-queue@0.1.0: {} 4161 4896 4162 4897 zimmerframe@1.1.4: {} 4898 + 4899 + zwitch@2.0.4: {}