- Owner / Founder
+ Owner
- Open-source tools and community assets published under the Cloud2BR organization.
+ Cloud2BR is the publishing organization behind the site, release assets, and
+ open-source project distribution.
-
- Project context
+
+ v0.0.1 release
+ Release context
- Format: Electron desktop app (HTML, CSS, JS)
- Hosting: GitHub Pages for this site, GitHub Releases for binaries
- Dev workflow: Container-only (Docker + Makefile)
- Purpose: Local-first Markdown documentation workspace
+ Product: Electron desktop documentation workspace
+ Release channel: GitHub Releases with tagged binaries and installers
+ Presentation: GitHub Pages landing site for downloads and project overview
+ Scope: Initial public release for macOS, Windows, and Linux
diff --git a/docs/roadmap.html b/docs/roadmap.html
new file mode 100644
index 0000000..9fab718
--- /dev/null
+++ b/docs/roadmap.html
@@ -0,0 +1,401 @@
+
+
+
+
+
+
+
+
Docs Foundry Roadmap
+
+
+
+
+
+
+
+
+
+
+
+
Docs Foundry roadmap
+
v0.0.1 Roadmap and Release Progress
+
+ This GitHub Pages view keeps shipped work separate from upcoming work and presents
+ the forward plan with ETA-style product signals, similar to how many ISVs present a
+ public roadmap.
+
+
+
+
🌙
+
+
+
+
+
+
+
+
+
Already in v0.0.1
+
Shipped and validated
+
+ These capabilities are now part of the current DocFoundry desktop experience and should
+ no longer be treated as roadmap-only items.
+
+
+
+
+ Editor workflow
+ Git-aware writing flow
+ Git change indicators, per-file diff view, synchronized preview scrolling, and drag-and-drop asset insertion now support the core editing workflow.
+
+
+ Docs intelligence
+ Safer link and content handling
+ Broken-link checks, link autocomplete, and native spell check now help catch documentation issues earlier during authoring.
+
+
+ Output formats
+ Richer preview and export
+ PDF export and locally bundled Mermaid rendering extend the app beyond plain Markdown preview into shareable and diagram-aware documentation workflows.
+
+
+
+
+
+
+
Product roadmap
+
ETA for upcoming features
+
+ This forward-looking roadmap keeps the shipped work above and uses ETA-style windows
+ for the next feature areas people can expect to see evolve.
+
+
+
+
+ ETA: Apr-May 2026
+ Publishing workflow
+ These items extend DocFoundry beyond editing into fuller documentation publishing and release packaging flows.
+
+ Export profiles for HTML, PDF, and release bundles
+ Customizable document templates
+ Release-ready packaging notes per output format
+
+
+
+ ETA: May 2026
+ Workspace intelligence
+ These features deepen the current authoring flow by helping users reason about structure, references, and document quality at scale.
+
+ Document diagnostics panel with grouped issues
+ Reference explorer for links, images, and footnotes
+ Workspace-level content quality reports
+
+
+
+ ETA: Jun 2026
+ Team collaboration
+ These items move the product closer to shared documentation workflows without turning it into a full IDE.
+
+ Shared review annotations
+ Comment-ready export output
+ Improved Git workflow summaries across a workspace
+
+
+
+ ETA: Ongoing
+ Public roadmap maintenance
+ The roadmap should stay current, release-aware, and transparent as new features ship and priorities shift.
+
+ Link roadmap items to future releases
+ Highlight recently shipped features
+ Group work by release milestone
+
+
+
+
+
+
+
+
+
Feedback channel
+
Tell us what to add or improve
+
+ The feedback flow is designed so requests can be saved as GitHub issues in a separate
+ feedback repo instead of landing in the main source repository. That keeps product
+ requests visible without blocking release work or protected branches.
+
+
+
+
+ Features
+ Request something new
+ Send a feature request for a capability, workflow, integration, or platform improvement.
+
+
+ Improvements
+ Refine what exists
+ Submit improvements for current workflows such as exports, navigation, preview quality, or packaging.
+
+
+ Docs & roadmap
+ Improve communication
+ Share onboarding gaps, documentation fixes, roadmap feedback, and prioritization suggestions.
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eslint.config.mjs b/eslint.config.mjs
index b633a2f..bab2d4a 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -14,6 +14,10 @@ export default [
localStorage: 'readonly',
CSS: 'readonly',
console: 'readonly',
+ setTimeout: 'readonly',
+ clearTimeout: 'readonly',
+ queueMicrotask: 'readonly',
+ URL: 'readonly',
// node
require: 'readonly',
module: 'readonly',
diff --git a/metrics.json b/metrics.json
index 8963073..61c28be 100644
--- a/metrics.json
+++ b/metrics.json
@@ -66,12 +66,17 @@
},
{
"date": "2026-04-11",
- "count": 2,
- "uniques": 2
+ "count": 0,
+ "uniques": 0
},
{
"date": "2026-04-12",
- "count": 1,
- "uniques": 1
+ "count": 0,
+ "uniques": 0
+ },
+ {
+ "date": "2026-04-13",
+ "count": 0,
+ "uniques": 0
}
]
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 2772e41..2c251b4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,8 @@
"version": "0.0.1",
"license": "MIT",
"dependencies": {
- "electron-updater": "^6.3.0"
+ "electron-updater": "^6.3.0",
+ "mermaid": "^10.9.1"
},
"devDependencies": {
"electron": "^35.0.0",
@@ -20,6 +21,12 @@
"vitest": "^3.0.0"
}
},
+ "node_modules/@braintree/sanitize-url": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz",
+ "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==",
+ "license": "MIT"
+ },
"node_modules/@develar/schema-utils": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
@@ -1606,11 +1613,31 @@
"assertion-error": "^2.0.1"
}
},
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-scale-chromatic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "license": "MIT"
+ },
"node_modules/@types/debug": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
"integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@types/ms": "*"
@@ -1664,11 +1691,19 @@
"@types/node": "*"
}
},
+ "node_modules/@types/mdast": {
+ "version": "3.0.15",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz",
+ "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2"
+ }
+ },
"node_modules/@types/ms": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
- "dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
@@ -1703,6 +1738,19 @@
"@types/node": "*"
}
},
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
+ "license": "MIT"
+ },
"node_modules/@types/verror": {
"version": "1.10.11",
"resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz",
@@ -2541,6 +2589,16 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/character-entities": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/check-error": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
@@ -2798,6 +2856,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cose-base": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
+ "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
+ "license": "MIT",
+ "dependencies": {
+ "layout-base": "^1.0.0"
+ }
+ },
"node_modules/crc": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz",
@@ -2853,222 +2920,749 @@
"node": ">= 8"
}
},
- "node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "node_modules/cytoscape": {
+ "version": "3.33.2",
+ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.2.tgz",
+ "integrity": "sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==",
"license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
"engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
+ "node": ">=0.10"
}
},
- "node_modules/decompress-response": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
- "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
- "dev": true,
+ "node_modules/cytoscape-cose-bilkent": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
+ "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
"license": "MIT",
"dependencies": {
- "mimic-response": "^3.1.0"
- },
- "engines": {
- "node": ">=10"
+ "cose-base": "^1.0.0"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "peerDependencies": {
+ "cytoscape": "^3.2.0"
}
},
- "node_modules/decompress-response/node_modules/mimic-response": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
- "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
+ "node_modules/d3": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
+ "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "3",
+ "d3-axis": "3",
+ "d3-brush": "3",
+ "d3-chord": "3",
+ "d3-color": "3",
+ "d3-contour": "4",
+ "d3-delaunay": "6",
+ "d3-dispatch": "3",
+ "d3-drag": "3",
+ "d3-dsv": "3",
+ "d3-ease": "3",
+ "d3-fetch": "3",
+ "d3-force": "3",
+ "d3-format": "3",
+ "d3-geo": "3",
+ "d3-hierarchy": "3",
+ "d3-interpolate": "3",
+ "d3-path": "3",
+ "d3-polygon": "3",
+ "d3-quadtree": "3",
+ "d3-random": "3",
+ "d3-scale": "4",
+ "d3-scale-chromatic": "3",
+ "d3-selection": "3",
+ "d3-shape": "3",
+ "d3-time": "3",
+ "d3-time-format": "4",
+ "d3-timer": "3",
+ "d3-transition": "3",
+ "d3-zoom": "3"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "engines": {
+ "node": ">=12"
}
},
- "node_modules/deep-eql": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
- "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
- "dev": true,
- "license": "MIT",
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "license": "ISC",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
"engines": {
- "node": ">=6"
+ "node": ">=12"
}
},
- "node_modules/deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/defer-to-connect": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
- "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
- "dev": true,
- "license": "MIT",
+ "node_modules/d3-axis": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz",
+ "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
+ "license": "ISC",
"engines": {
- "node": ">=10"
+ "node": ">=12"
}
},
- "node_modules/define-data-property": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
- "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
- "dev": true,
- "license": "MIT",
- "optional": true,
+ "node_modules/d3-brush": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz",
+ "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
+ "license": "ISC",
"dependencies": {
- "es-define-property": "^1.0.0",
- "es-errors": "^1.3.0",
- "gopd": "^1.0.1"
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "3",
+ "d3-transition": "3"
},
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=12"
}
},
- "node_modules/define-properties": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
- "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
+ "node_modules/d3-chord": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz",
+ "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
+ "license": "ISC",
"dependencies": {
- "define-data-property": "^1.0.1",
- "has-property-descriptors": "^1.0.0",
- "object-keys": "^1.1.1"
+ "d3-path": "1 - 3"
},
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=12"
}
},
- "node_modules/delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
- "dev": true,
- "license": "MIT",
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "license": "ISC",
"engines": {
- "node": ">=0.4.0"
+ "node": ">=12"
}
},
- "node_modules/detect-node": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
- "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/dir-compare": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-3.3.0.tgz",
- "integrity": "sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==",
- "dev": true,
- "license": "MIT",
+ "node_modules/d3-contour": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz",
+ "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
+ "license": "ISC",
"dependencies": {
- "buffer-equal": "^1.0.0",
- "minimatch": "^3.0.4"
+ "d3-array": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=12"
}
},
- "node_modules/dir-compare/node_modules/brace-expansion": {
- "version": "1.1.13",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
- "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
- "dev": true,
- "license": "MIT",
+ "node_modules/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
+ "license": "ISC",
"dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "delaunator": "5"
+ },
+ "engines": {
+ "node": ">=12"
}
},
- "node_modules/dir-compare/node_modules/minimatch": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
- "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
- "dev": true,
+ "node_modules/d3-dispatch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+ "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-drag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
+ "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
"license": "ISC",
"dependencies": {
- "brace-expansion": "^1.1.7"
+ "d3-dispatch": "1 - 3",
+ "d3-selection": "3"
},
"engines": {
- "node": "*"
+ "node": ">=12"
}
},
- "node_modules/dmg-builder": {
- "version": "24.13.3",
- "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.13.3.tgz",
- "integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==",
- "dev": true,
- "license": "MIT",
+ "node_modules/d3-dsv": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
+ "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
+ "license": "ISC",
"dependencies": {
- "app-builder-lib": "24.13.3",
- "builder-util": "24.13.1",
- "builder-util-runtime": "9.2.4",
- "fs-extra": "^10.1.0",
- "iconv-lite": "^0.6.2",
- "js-yaml": "^4.1.0"
+ "commander": "7",
+ "iconv-lite": "0.6",
+ "rw": "1"
},
- "optionalDependencies": {
- "dmg-license": "^1.0.11"
+ "bin": {
+ "csv2json": "bin/dsv2json.js",
+ "csv2tsv": "bin/dsv2dsv.js",
+ "dsv2dsv": "bin/dsv2dsv.js",
+ "dsv2json": "bin/dsv2json.js",
+ "json2csv": "bin/json2dsv.js",
+ "json2dsv": "bin/json2dsv.js",
+ "json2tsv": "bin/json2dsv.js",
+ "tsv2csv": "bin/dsv2dsv.js",
+ "tsv2json": "bin/dsv2json.js"
+ },
+ "engines": {
+ "node": ">=12"
}
},
- "node_modules/dmg-builder/node_modules/fs-extra": {
- "version": "10.1.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
- "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
- "dev": true,
+ "node_modules/d3-dsv/node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-fetch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz",
+ "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
+ "license": "ISC",
"dependencies": {
- "graceful-fs": "^4.2.0",
- "jsonfile": "^6.0.1",
- "universalify": "^2.0.0"
+ "d3-dsv": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
- "node_modules/dmg-builder/node_modules/jsonfile": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
- "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
- "dev": true,
- "license": "MIT",
+ "node_modules/d3-force": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
+ "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
+ "license": "ISC",
"dependencies": {
- "universalify": "^2.0.0"
+ "d3-dispatch": "1 - 3",
+ "d3-quadtree": "1 - 3",
+ "d3-timer": "1 - 3"
},
- "optionalDependencies": {
- "graceful-fs": "^4.1.6"
+ "engines": {
+ "node": ">=12"
}
},
- "node_modules/dmg-builder/node_modules/universalify": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
- "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
- "dev": true,
- "license": "MIT",
+ "node_modules/d3-format": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
+ "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-geo": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
+ "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.5.0 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-hierarchy": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
+ "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-polygon": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz",
+ "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-quadtree": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
+ "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-random": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz",
+ "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-sankey": {
+ "version": "0.12.3",
+ "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz",
+ "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "d3-array": "1 - 2",
+ "d3-shape": "^1.2.0"
+ }
+ },
+ "node_modules/d3-sankey/node_modules/d3-array": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "internmap": "^1.0.0"
+ }
+ },
+ "node_modules/d3-sankey/node_modules/d3-path": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
+ "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/d3-sankey/node_modules/d3-shape": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
+ "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "d3-path": "1"
+ }
+ },
+ "node_modules/d3-sankey/node_modules/internmap": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==",
+ "license": "ISC"
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale-chromatic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-interpolate": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-selection": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
+ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-transition": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
+ "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-dispatch": "1 - 3",
+ "d3-ease": "1 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "d3-selection": "2 - 3"
+ }
+ },
+ "node_modules/d3-zoom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
+ "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "2 - 3",
+ "d3-transition": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/dagre-d3-es": {
+ "version": "7.0.13",
+ "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz",
+ "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "d3": "^7.9.0",
+ "lodash-es": "^4.17.21"
+ }
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.20",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz",
+ "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decode-named-character-reference": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz",
+ "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/decompress-response/node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/defer-to-connect": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
+ "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delaunator": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz",
+ "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==",
+ "license": "ISC",
+ "dependencies": {
+ "robust-predicates": "^3.0.2"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/detect-node": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
+ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/diff": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz",
+ "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/dir-compare": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-3.3.0.tgz",
+ "integrity": "sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal": "^1.0.0",
+ "minimatch": "^3.0.4"
+ }
+ },
+ "node_modules/dir-compare/node_modules/brace-expansion": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
+ "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/dir-compare/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/dmg-builder": {
+ "version": "24.13.3",
+ "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.13.3.tgz",
+ "integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "app-builder-lib": "24.13.3",
+ "builder-util": "24.13.1",
+ "builder-util-runtime": "9.2.4",
+ "fs-extra": "^10.1.0",
+ "iconv-lite": "^0.6.2",
+ "js-yaml": "^4.1.0"
+ },
+ "optionalDependencies": {
+ "dmg-license": "^1.0.11"
+ }
+ },
+ "node_modules/dmg-builder/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/dmg-builder/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
+ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/dmg-builder/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
@@ -3100,6 +3694,15 @@
"node": ">=8"
}
},
+ "node_modules/dompurify": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz",
+ "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
"node_modules/dotenv": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz",
@@ -3434,6 +4037,12 @@
"node": ">= 10.0.0"
}
},
+ "node_modules/elkjs": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz",
+ "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==",
+ "license": "EPL-2.0"
+ },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -4444,7 +5053,6 @@
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -4530,6 +5138,15 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -4732,6 +5349,31 @@
"graceful-fs": "^4.1.6"
}
},
+ "node_modules/katex": {
+ "version": "0.16.45",
+ "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.45.tgz",
+ "integrity": "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==",
+ "funding": [
+ "https://opencollective.com/katex",
+ "https://github.com/sponsors/katex"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^8.3.0"
+ },
+ "bin": {
+ "katex": "cli.js"
+ }
+ },
+ "node_modules/katex/node_modules/commander": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -4742,6 +5384,26 @@
"json-buffer": "3.0.1"
}
},
+ "node_modules/khroma": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz",
+ "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
+ },
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/layout-base": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
+ "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==",
+ "license": "MIT"
+ },
"node_modules/lazy-val": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
@@ -4835,130 +5497,643 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/lodash-es": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
+ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
+ "license": "MIT"
+ },
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"dev": true,
"license": "MIT",
- "peer": true
+ "peer": true
+ },
+ "node_modules/lodash.difference": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
+ "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash.escaperegexp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
+ "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.flatten": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
+ "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash.isequal": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
+ "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.union": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
+ "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/loupe": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
+ "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lowercase-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
+ "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/matcher": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
+ "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "escape-string-regexp": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mdast-util-from-markdown": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz",
+ "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "@types/unist": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "mdast-util-to-string": "^3.1.0",
+ "micromark": "^3.0.0",
+ "micromark-util-decode-numeric-character-reference": "^1.0.0",
+ "micromark-util-decode-string": "^1.0.0",
+ "micromark-util-normalize-identifier": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "unist-util-stringify-position": "^3.0.0",
+ "uvu": "^0.5.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-string": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz",
+ "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mermaid": {
+ "version": "10.9.5",
+ "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.9.5.tgz",
+ "integrity": "sha512-eRlKEjzak4z1rcXeCd1OAlyawhrptClQDo8OuI8n6bSVqJ9oMfd5Lrf3Q+TdJHewi/9AIOc3UmEo8Fz+kNzzuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@braintree/sanitize-url": "^6.0.1",
+ "@types/d3-scale": "^4.0.3",
+ "@types/d3-scale-chromatic": "^3.0.0",
+ "cytoscape": "^3.28.1",
+ "cytoscape-cose-bilkent": "^4.1.0",
+ "d3": "^7.4.0",
+ "d3-sankey": "^0.12.3",
+ "dagre-d3-es": "7.0.13",
+ "dayjs": "^1.11.7",
+ "dompurify": "^3.2.4",
+ "elkjs": "^0.9.0",
+ "katex": "^0.16.9",
+ "khroma": "^2.0.0",
+ "lodash-es": "^4.17.21",
+ "mdast-util-from-markdown": "^1.3.0",
+ "non-layered-tidy-tree-layout": "^2.0.2",
+ "stylis": "^4.1.3",
+ "ts-dedent": "^2.2.0",
+ "uuid": "^9.0.0",
+ "web-worker": "^1.2.0"
+ }
+ },
+ "node_modules/micromark": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz",
+ "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@types/debug": "^4.0.0",
+ "debug": "^4.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-core-commonmark": "^1.0.1",
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-combine-extensions": "^1.0.0",
+ "micromark-util-decode-numeric-character-reference": "^1.0.0",
+ "micromark-util-encode": "^1.0.0",
+ "micromark-util-normalize-identifier": "^1.0.0",
+ "micromark-util-resolve-all": "^1.0.0",
+ "micromark-util-sanitize-uri": "^1.0.0",
+ "micromark-util-subtokenize": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.1",
+ "uvu": "^0.5.0"
+ }
+ },
+ "node_modules/micromark-core-commonmark": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz",
+ "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-factory-destination": "^1.0.0",
+ "micromark-factory-label": "^1.0.0",
+ "micromark-factory-space": "^1.0.0",
+ "micromark-factory-title": "^1.0.0",
+ "micromark-factory-whitespace": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-classify-character": "^1.0.0",
+ "micromark-util-html-tag-name": "^1.0.0",
+ "micromark-util-normalize-identifier": "^1.0.0",
+ "micromark-util-resolve-all": "^1.0.0",
+ "micromark-util-subtokenize": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.1",
+ "uvu": "^0.5.0"
+ }
+ },
+ "node_modules/micromark-factory-destination": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz",
+ "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-factory-label": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz",
+ "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "node_modules/micromark-factory-space": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz",
+ "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-factory-title": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz",
+ "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-factory-whitespace": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz",
+ "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz",
+ "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
},
- "node_modules/lodash.difference": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
- "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==",
- "dev": true,
+ "node_modules/micromark-util-chunked": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz",
+ "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
"license": "MIT",
- "peer": true
+ "dependencies": {
+ "micromark-util-symbol": "^1.0.0"
+ }
},
- "node_modules/lodash.escaperegexp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
- "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
- "license": "MIT"
+ "node_modules/micromark-util-classify-character": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz",
+ "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
},
- "node_modules/lodash.flatten": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
- "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
- "dev": true,
+ "node_modules/micromark-util-combine-extensions": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz",
+ "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
"license": "MIT",
- "peer": true
+ "dependencies": {
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
},
- "node_modules/lodash.isequal": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
- "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
- "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
- "license": "MIT"
+ "node_modules/micromark-util-decode-numeric-character-reference": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz",
+ "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^1.0.0"
+ }
},
- "node_modules/lodash.isplainobject": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
- "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
- "dev": true,
+ "node_modules/micromark-util-decode-string": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz",
+ "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
"license": "MIT",
- "peer": true
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-decode-numeric-character-reference": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0"
+ }
},
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true,
+ "node_modules/micromark-util-encode": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz",
+ "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
"license": "MIT"
},
- "node_modules/lodash.union": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
- "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "node_modules/loupe": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
- "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
- "dev": true,
+ "node_modules/micromark-util-html-tag-name": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz",
+ "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
"license": "MIT"
},
- "node_modules/lowercase-keys": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
- "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
- "dev": true,
+ "node_modules/micromark-util-normalize-identifier": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz",
+ "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
"license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
- "license": "ISC",
"dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
+ "micromark-util-symbol": "^1.0.0"
}
},
- "node_modules/magic-string": {
- "version": "0.30.21",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
- "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
- "dev": true,
+ "node_modules/micromark-util-resolve-all": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz",
+ "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
"license": "MIT",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.5"
+ "micromark-util-types": "^1.0.0"
}
},
- "node_modules/matcher": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
- "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
- "dev": true,
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz",
+ "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
"license": "MIT",
- "optional": true,
"dependencies": {
- "escape-string-regexp": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-encode": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0"
}
},
- "node_modules/math-intrinsics": {
+ "node_modules/micromark-util-subtokenize": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "dev": true,
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz",
+ "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
"license": "MIT",
- "engines": {
- "node": ">= 0.4"
+ "dependencies": {
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
}
},
+ "node_modules/micromark-util-symbol": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz",
+ "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-types": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz",
+ "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
@@ -5078,6 +6253,15 @@
"node": ">=10"
}
},
+ "node_modules/mri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -5118,6 +6302,12 @@
"license": "MIT",
"optional": true
},
+ "node_modules/non-layered-tidy-tree-layout": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz",
+ "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==",
+ "license": "MIT"
+ },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -5603,6 +6793,12 @@
"node": ">=8.0"
}
},
+ "node_modules/robust-predicates": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz",
+ "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==",
+ "license": "Unlicense"
+ },
"node_modules/rollup": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
@@ -5648,6 +6844,24 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/rw": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/sade": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
+ "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
+ "license": "MIT",
+ "dependencies": {
+ "mri": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -5674,7 +6888,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "dev": true,
"license": "MIT"
},
"node_modules/sanitize-filename": {
@@ -5986,6 +7199,12 @@
"url": "https://github.com/sponsors/antfu"
}
},
+ "node_modules/stylis": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
+ "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
+ "license": "MIT"
+ },
"node_modules/sumchecker": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
@@ -6239,6 +7458,15 @@
"utf8-byte-length": "^1.0.1"
}
},
+ "node_modules/ts-dedent": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
+ "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.10"
+ }
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -6287,6 +7515,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/unist-util-stringify-position": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz",
+ "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
@@ -6322,6 +7563,37 @@
"license": "MIT",
"peer": true
},
+ "node_modules/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/uvu": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz",
+ "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.0",
+ "diff": "^5.0.0",
+ "kleur": "^4.0.3",
+ "sade": "^1.7.3"
+ },
+ "bin": {
+ "uvu": "bin.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/verror": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
@@ -6553,6 +7825,12 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/web-worker": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz",
+ "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==",
+ "license": "Apache-2.0"
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/package.json b/package.json
index 2e0e922..50a41a1 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,8 @@
}
},
"dependencies": {
- "electron-updater": "^6.3.0"
+ "electron-updater": "^6.3.0",
+ "mermaid": "^10.9.1"
},
"devDependencies": {
"electron": "^35.0.0",
diff --git a/src/lib/workspace-path.js b/src/lib/workspace-path.js
index 12e7fb0..7746589 100644
--- a/src/lib/workspace-path.js
+++ b/src/lib/workspace-path.js
@@ -1,9 +1,19 @@
const path = require('path');
const fs = require('fs');
-const INVALID_NAME_CHARS = /[\x00-\x1f<>:"|?*]/;
+const INVALID_NAME_CHARS = /[<>:"|?*]/;
const WINDOWS_RESERVED = /^(con|prn|aux|nul|com\d|lpt\d)$/i;
+function hasControlCharacters(value) {
+ for (const character of value) {
+ const codePoint = character.codePointAt(0);
+ if (typeof codePoint === 'number' && codePoint <= 31) {
+ return true;
+ }
+ }
+ return false;
+}
+
function resolveWorkspacePath(workspaceRoot, candidatePath) {
const resolvedPath = path.resolve(candidatePath);
if (!workspaceRoot) return resolvedPath;
@@ -53,7 +63,7 @@ function validateFileName(name) {
if (trimmed !== name || trimmed.endsWith('.') || trimmed.endsWith(' ')) {
throw new Error('File name has invalid leading/trailing characters');
}
- if (INVALID_NAME_CHARS.test(trimmed)) {
+ if (hasControlCharacters(trimmed) || INVALID_NAME_CHARS.test(trimmed)) {
throw new Error('File name contains invalid characters');
}
if (trimmed.includes('/') || trimmed.includes('\\')) {
diff --git a/src/main.js b/src/main.js
index b65c1ea..eb78c75 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,6 +1,8 @@
const { app, BrowserWindow, ipcMain, dialog, Menu } = require('electron');
const path = require('path');
const fs = require('fs');
+const os = require('os');
+const { execFileSync } = require('child_process');
const { resolveWorkspacePath, validateFileName } = require('./lib/workspace-path');
// Hot reload in development
@@ -21,6 +23,16 @@ let hasUnsavedChanges = false;
let allowWindowClose = false;
let fileWatcher = null;
+function hasControlCharacters(value) {
+ for (const character of value) {
+ const codePoint = character.codePointAt(0);
+ if (typeof codePoint === 'number' && codePoint <= 31) {
+ return true;
+ }
+ }
+ return false;
+}
+
function createWindow() {
allowWindowClose = false;
hasUnsavedChanges = false;
@@ -36,7 +48,8 @@ function createWindow() {
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
- nodeIntegration: false
+ nodeIntegration: false,
+ spellcheck: true
}
});
@@ -70,6 +83,7 @@ function buildAppMenu() {
{ type: 'separator' },
{ label: 'Save', accelerator: 'CmdOrCtrl+S', click: () => mainWindow.webContents.send('menu-save') },
{ label: 'Export HTML…', accelerator: 'CmdOrCtrl+Shift+E', click: () => mainWindow.webContents.send('menu-export-html') },
+ { label: 'Export PDF…', accelerator: 'CmdOrCtrl+Shift+P', click: () => mainWindow.webContents.send('menu-export-pdf') },
{ type: 'separator' },
isMac ? { role: 'close' } : { role: 'quit' }
]
@@ -93,6 +107,8 @@ function buildAppMenu() {
submenu: [
{ label: 'Command Palette…', accelerator: 'CmdOrCtrl+P', click: () => mainWindow.webContents.send('menu-command-palette') },
{ label: 'Workspace Search…', accelerator: 'CmdOrCtrl+Shift+F', click: () => mainWindow.webContents.send('menu-workspace-search') },
+ { label: 'Git Diff…', accelerator: 'CmdOrCtrl+Shift+D', click: () => mainWindow.webContents.send('menu-git-diff') },
+ { label: 'Check Broken Links…', accelerator: 'CmdOrCtrl+Shift+L', click: () => mainWindow.webContents.send('menu-check-links') },
{ type: 'separator' },
{ label: 'Toggle Sidebar', accelerator: 'CmdOrCtrl+B', click: () => mainWindow.webContents.send('menu-toggle-sidebar') },
{ label: 'Toggle Outline', accelerator: 'CmdOrCtrl+Shift+O', click: () => mainWindow.webContents.send('menu-toggle-outline') },
@@ -366,6 +382,110 @@ ipcMain.handle('export-html', async (_event, htmlContent, suggestedName) => {
return true;
});
+ipcMain.handle('export-pdf', async (_event, htmlContent, suggestedName) => {
+ if (!mainWindow) return false;
+
+ const result = await dialog.showSaveDialog(mainWindow, {
+ title: 'Export PDF',
+ defaultPath: suggestedName || 'document.pdf',
+ filters: [{ name: 'PDF Files', extensions: ['pdf'] }]
+ });
+ if (result.canceled || !result.filePath) return false;
+
+ const pdfWindow = new BrowserWindow({
+ show: false,
+ webPreferences: {
+ sandbox: true,
+ contextIsolation: true,
+ nodeIntegration: false
+ }
+ });
+
+ const fullHtml = `
${htmlContent}`;
+
+ try {
+ await pdfWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(fullHtml)}`);
+ const pdf = await pdfWindow.webContents.printToPDF({ printBackground: true, preferCSSPageSize: true });
+ fs.writeFileSync(result.filePath, pdf);
+ return true;
+ } finally {
+ if (!pdfWindow.isDestroyed()) {
+ pdfWindow.destroy();
+ }
+ }
+});
+
+ipcMain.handle('get-git-status', async () => {
+ const repoRoot = findGitRoot(currentFolder);
+ if (!repoRoot) return { available: false, repoRoot: null, entries: [] };
+
+ try {
+ const output = execFileSync('git', ['-C', repoRoot, 'status', '--porcelain', '--untracked-files=all'], {
+ encoding: 'utf-8'
+ });
+ const entries = output
+ .split('\n')
+ .filter(Boolean)
+ .map(line => parseGitStatusLine(line, repoRoot));
+
+ return { available: true, repoRoot, entries };
+ } catch (_) {
+ return { available: false, repoRoot, entries: [] };
+ }
+});
+
+ipcMain.handle('get-git-diff', async (_event, filePath) => {
+ const repoRoot = findGitRoot(currentFolder);
+ if (!repoRoot || !filePath) return { available: false, diff: '', status: 'unavailable' };
+
+ const resolved = resolveWorkspacePath(currentFolder, filePath);
+ const relative = path.relative(repoRoot, resolved);
+
+ try {
+ const statusOutput = execFileSync('git', ['-C', repoRoot, 'status', '--porcelain', '--', relative], {
+ encoding: 'utf-8'
+ }).trim();
+ const status = statusOutput ? statusOutput.slice(0, 2).trim() || '?' : 'clean';
+
+ let diff = execFileSync('git', ['-C', repoRoot, 'diff', '--no-color', '--', relative], {
+ encoding: 'utf-8'
+ });
+
+ if (!diff.trim() && status === '??') {
+ const content = fs.readFileSync(resolved, 'utf-8');
+ diff = `Untracked file: ${relative}${os.EOL}${os.EOL}${content}`;
+ }
+
+ return { available: true, diff, status, repoRoot, relativePath: relative };
+ } catch (error) {
+ return { available: false, diff: '', status: 'error', message: error.message };
+ }
+});
+
+ipcMain.handle('import-files-into-workspace', async (_event, sources, targetDir) => {
+ if (!Array.isArray(sources) || sources.length === 0) return [];
+ const resolvedDir = resolveWorkspacePath(currentFolder, targetDir || currentFolder);
+ fs.mkdirSync(resolvedDir, { recursive: true });
+
+ return sources.map(sourcePath => {
+ const sourceName = path.basename(sourcePath);
+ const targetPath = createAvailablePath(resolvedDir, sourceName);
+ fs.copyFileSync(sourcePath, targetPath);
+
+ return {
+ sourcePath,
+ path: targetPath,
+ name: path.basename(targetPath),
+ relativePath: currentFolder ? path.relative(currentFolder, targetPath) : path.basename(targetPath),
+ isImage: /\.(png|jpe?g|gif|webp|svg)$/i.test(targetPath)
+ };
+ });
+});
+
// ── IPC: prompt for new file/folder name ──────────────────────────────────────
ipcMain.handle('prompt-new-file', async (_event, parentDir) => {
@@ -387,13 +507,13 @@ function startFileWatcher(folderPath) {
try {
fileWatcher = fs.watch(folderPath, { recursive: true }, (eventType, fileName) => {
if (!fileName) return;
- // Sanitize: reject null bytes and control characters
- if (/[\x00-\x1f]/.test(fileName)) return;
+ const normalizedFileName = String(fileName);
+ if (hasControlCharacters(normalizedFileName)) return;
const IGNORE_PATTERNS = ['.git', 'node_modules', '.DS_Store'];
- if (IGNORE_PATTERNS.some(p => fileName.includes(p))) return;
+ if (IGNORE_PATTERNS.some(p => normalizedFileName.includes(p))) return;
if (mainWindow && !mainWindow.isDestroyed()) {
- mainWindow.webContents.send('workspace-changed', { eventType, fileName: String(fileName) });
+ mainWindow.webContents.send('workspace-changed', { eventType, fileName: normalizedFileName });
}
});
} catch (_) {
@@ -441,6 +561,40 @@ function readFolderTree(rootPath) {
return { root: rootPath, name: path.basename(rootPath), children: walk(rootPath, 0) };
}
+function findGitRoot(startPath) {
+ if (!startPath) return null;
+ try {
+ return execFileSync('git', ['-C', startPath, 'rev-parse', '--show-toplevel'], {
+ encoding: 'utf-8'
+ }).trim();
+ } catch (_) {
+ return null;
+ }
+}
+
+function parseGitStatusLine(line, repoRoot) {
+ const status = line.slice(0, 2);
+ const filePath = line.slice(3).trim();
+ return {
+ status,
+ path: path.join(repoRoot, filePath),
+ relativePath: filePath
+ };
+}
+
+function createAvailablePath(dirPath, fileName) {
+ const parsed = path.parse(fileName);
+ let candidate = path.join(dirPath, fileName);
+ let counter = 1;
+
+ while (fs.existsSync(candidate)) {
+ candidate = path.join(dirPath, `${parsed.name}-${counter}${parsed.ext}`);
+ counter += 1;
+ }
+
+ return candidate;
+}
+
// ── App lifecycle ─────────────────────────────────────────────────────────────
app.whenReady().then(() => {
diff --git a/src/preload.js b/src/preload.js
index 5bf4d8f..5caeab8 100644
--- a/src/preload.js
+++ b/src/preload.js
@@ -26,6 +26,14 @@ contextBridge.exposeInMainWorld('docfoundry', {
// Export
exportHtml: (htmlContent, suggestedName) => ipcRenderer.invoke('export-html', htmlContent, suggestedName),
+ exportPdf: (htmlContent, suggestedName) => ipcRenderer.invoke('export-pdf', htmlContent, suggestedName),
+
+ // Git
+ getGitStatus: () => ipcRenderer.invoke('get-git-status'),
+ getGitDiff: (filePath) => ipcRenderer.invoke('get-git-diff', filePath),
+
+ // File import
+ importFilesIntoWorkspace: (sources, targetDir) => ipcRenderer.invoke('import-files-into-workspace', sources, targetDir),
// Dirty state
setDirtyState: (isDirty) => ipcRenderer.send('set-dirty-state', isDirty),
@@ -38,6 +46,7 @@ contextBridge.exposeInMainWorld('docfoundry', {
onMenuEvent: (channel, callback) => {
const validChannels = [
'menu-open-folder', 'menu-new-file', 'menu-save', 'menu-export-html',
+ 'menu-export-pdf', 'menu-git-diff', 'menu-check-links',
'menu-find', 'menu-replace', 'menu-command-palette', 'menu-workspace-search',
'menu-toggle-sidebar', 'menu-toggle-outline', 'menu-zen-mode', 'menu-shortcuts'
];
diff --git a/src/renderer/index.html b/src/renderer/index.html
index b4fe136..6eb8b37 100644
--- a/src/renderer/index.html
+++ b/src/renderer/index.html
@@ -3,7 +3,7 @@
-
+
DocFoundry
@@ -52,7 +52,7 @@
Organize
Export
- Export your rendered documents to standalone HTML files.
+ Export your rendered documents to standalone HTML or PDF files.
@@ -126,7 +126,8 @@
Export
@@ -168,6 +169,26 @@
Export
+
+
+
+
@@ -183,6 +204,7 @@
File
Ctrl+S Save
Ctrl+W Close Tab
Ctrl+Shift+E Export HTML
+
Ctrl+Shift+P Export PDF
Edit
@@ -195,6 +217,8 @@
Edit
View
Ctrl+P Command Palette
Ctrl+Shift+F Workspace Search
+
Ctrl+Shift+D Git Diff
+
Ctrl+Shift+L Broken Link Check
Ctrl+B Toggle Sidebar
Ctrl+Shift+O Toggle Outline
Ctrl+Shift+Z Zen Mode
diff --git a/src/renderer/markdown.js b/src/renderer/markdown.js
index d84f5e3..acc9ba4 100644
--- a/src/renderer/markdown.js
+++ b/src/renderer/markdown.js
@@ -149,6 +149,9 @@
}
function renderCodeBlock(code, lang) {
+ if ((lang || '').toLowerCase() === 'mermaid') {
+ return `
`;
+ }
const languageClass = lang ? ` class="language-${escapeAttribute(lang)}"` : '';
return `
${escapeHtml(code)}`;
}
@@ -196,7 +199,7 @@
const header = lines[index].trim();
const separator = lines[index + 1].trim();
- return header.includes('|') && /^\|?\s*[:\-]+(?:\s*\|\s*[:\-]+)+\s*\|?$/.test(separator);
+ return header.includes('|') && /^\|?\s*[:-]+(?:\s*\|\s*[:-]+)+\s*\|?$/.test(separator);
}
function renderTable(lines, startIndex) {
@@ -296,7 +299,7 @@
}
function escapeHtml(text) {
- return text.replace(/[&<>\"]/g, char => {
+ return text.replace(/[&<>"]/g, char => {
const entities = {
'&': '&',
'<': '<',
diff --git a/src/renderer/renderer.js b/src/renderer/renderer.js
index c518c2b..30f2234 100644
--- a/src/renderer/renderer.js
+++ b/src/renderer/renderer.js
@@ -12,6 +12,8 @@ const workspaceNameEl = document.getElementById('workspace-name');
const tabsScroll = document.getElementById('tabs-scroll');
const codeEditor = document.getElementById('code-editor');
const previewEl = document.getElementById('preview');
+const previewPane = document.querySelector('.pane-preview');
+const linkSuggestions = document.getElementById('link-suggestions');
const versionEl = document.getElementById('version');
const sidebar = document.getElementById('sidebar');
const sidebarResize = document.getElementById('sidebar-resize');
@@ -22,6 +24,7 @@ const statusCursor = document.getElementById('status-cursor');
const statusWords = document.getElementById('status-words');
const statusReading = document.getElementById('status-reading');
const statusAutosave = document.getElementById('status-autosave');
+const statusEncoding = document.getElementById('status-encoding');
const outlinePanel = document.getElementById('outline-panel');
const outlineTree = document.getElementById('outline-tree');
const btnCloseOutline = document.getElementById('btn-close-outline');
@@ -32,6 +35,13 @@ const searchPanel = document.getElementById('search-panel');
const searchInput = document.getElementById('search-input');
const searchResults = document.getElementById('search-results');
const searchClose = document.getElementById('search-close');
+const diffOverlay = document.getElementById('diff-overlay');
+const diffTitle = document.getElementById('diff-title');
+const diffOutput = document.getElementById('diff-output');
+const diffClose = document.getElementById('diff-close');
+const linkCheckOverlay = document.getElementById('link-check-overlay');
+const linkCheckResults = document.getElementById('link-check-results');
+const linkCheckClose = document.getElementById('link-check-close');
const findBar = document.getElementById('find-bar');
const findInput = document.getElementById('find-input');
const findCount = document.getElementById('find-count');
@@ -66,6 +76,13 @@ let zenMode = false;
let findMatches = [];
let findMatchIndex = -1;
let tabIdCounter = 0;
+let gitStatusMap = new Map();
+let brokenLinkResults = [];
+let previewSyncLock = null;
+let mermaidLoaderPromise = null;
+let suggestionItems = [];
+let suggestionIndex = 0;
+let suggestionRange = null;
// ── Version label ─────────────────────────────────────────────────────────────
if (versionEl && api) {
@@ -85,6 +102,8 @@ btnNewFolder.addEventListener('click', () => promptNewFolder());
btnSave.addEventListener('click', saveActiveTab);
btnCloseOutline.addEventListener('click', () => toggleOutline(false));
searchClose.addEventListener('click', () => toggleSearchPanel(false));
+diffClose.addEventListener('click', () => toggleDiffOverlay(false));
+linkCheckClose.addEventListener('click', () => toggleBrokenLinkOverlay(false));
findClose.addEventListener('click', () => toggleFindBar(false));
shortcutsClose.addEventListener('click', () => toggleShortcuts(false));
@@ -107,10 +126,13 @@ if (api?.onMenuEvent) {
api.onMenuEvent('menu-new-file', () => promptNewFile());
api.onMenuEvent('menu-save', saveActiveTab);
api.onMenuEvent('menu-export-html', exportCurrentHtml);
+ api.onMenuEvent('menu-export-pdf', exportCurrentPdf);
api.onMenuEvent('menu-find', () => toggleFindBar(true, false));
api.onMenuEvent('menu-replace', () => toggleFindBar(true, true));
api.onMenuEvent('menu-command-palette', () => toggleCommandPalette(true));
api.onMenuEvent('menu-workspace-search', () => toggleSearchPanel(true));
+ api.onMenuEvent('menu-git-diff', openGitDiff);
+ api.onMenuEvent('menu-check-links', () => runBrokenLinkCheck(true));
api.onMenuEvent('menu-toggle-sidebar', () => toggleSidebar());
api.onMenuEvent('menu-toggle-outline', () => toggleOutline());
api.onMenuEvent('menu-zen-mode', () => toggleZenMode());
@@ -127,6 +149,7 @@ if (api?.onWorkspaceChanged) {
if (tree) {
workspaceTree = tree;
renderFileTree(tree.children, fileTreeEl, 0);
+ refreshGitStatus();
}
}, 500);
});
@@ -157,6 +180,18 @@ document.addEventListener('keydown', (e) => {
return;
}
+ if (mod && e.shiftKey && e.key === 'D') {
+ e.preventDefault();
+ openGitDiff();
+ return;
+ }
+
+ if (mod && e.shiftKey && e.key === 'L') {
+ e.preventDefault();
+ runBrokenLinkCheck(true);
+ return;
+ }
+
// Ctrl+F — find
if (mod && e.key === 'f' && !e.shiftKey) {
e.preventDefault();
@@ -206,6 +241,12 @@ document.addEventListener('keydown', (e) => {
return;
}
+ if (mod && e.shiftKey && e.key === 'P') {
+ e.preventDefault();
+ exportCurrentPdf();
+ return;
+ }
+
// Ctrl+N — new file
if (mod && e.key === 'n' && !e.shiftKey) {
e.preventDefault();
@@ -237,6 +278,8 @@ document.addEventListener('keydown', (e) => {
// Escape — close overlays
if (e.key === 'Escape') {
+ if (!diffOverlay.hidden) { toggleDiffOverlay(false); return; }
+ if (!linkCheckOverlay.hidden) { toggleBrokenLinkOverlay(false); return; }
if (!commandPalette.hidden) { toggleCommandPalette(false); return; }
if (!searchPanel.hidden) { toggleSearchPanel(false); return; }
if (!shortcutsOverlay.hidden) { toggleShortcuts(false); return; }
@@ -247,6 +290,28 @@ document.addEventListener('keydown', (e) => {
// ── Tab key support in editor ─────────────────────────────────────────────────
codeEditor.addEventListener('keydown', (e) => {
+ if (!linkSuggestions.hidden) {
+ if (e.key === 'ArrowDown') {
+ e.preventDefault();
+ moveLinkSuggestion(1);
+ return;
+ }
+ if (e.key === 'ArrowUp') {
+ e.preventDefault();
+ moveLinkSuggestion(-1);
+ return;
+ }
+ if (e.key === 'Enter' || e.key === 'Tab') {
+ e.preventDefault();
+ applyLinkSuggestion();
+ return;
+ }
+ if (e.key === 'Escape') {
+ hideLinkSuggestions();
+ return;
+ }
+ }
+
if (e.key === 'Tab') {
e.preventDefault();
const start = codeEditor.selectionStart;
@@ -256,17 +321,15 @@ codeEditor.addEventListener('keydown', (e) => {
if (e.shiftKey) {
// Outdent: remove leading two spaces from each selected line
const before = value.substring(0, start);
- const selected = value.substring(start, end);
const lineStart = before.lastIndexOf('\n') + 1;
const block = value.substring(lineStart, end);
- const outdented = block.replace(/^ /gm, '');
+ const outdented = block.replace(/^ {2}/gm, '');
codeEditor.value = value.substring(0, lineStart) + outdented + value.substring(end);
codeEditor.selectionStart = start - (block.length - outdented.length > 0 ? Math.min(2, start - lineStart) : 0);
codeEditor.selectionEnd = lineStart + outdented.length;
} else if (start !== end) {
// Indent selected lines
const before = value.substring(0, start);
- const selected = value.substring(start, end);
const lineStart = before.lastIndexOf('\n') + 1;
const block = value.substring(lineStart, end);
const indented = block.replace(/^/gm, ' ');
@@ -285,16 +348,42 @@ codeEditor.addEventListener('keydown', (e) => {
// ── Editor input handling ─────────────────────────────────────────────────────
codeEditor.addEventListener('input', onEditorInput);
-codeEditor.addEventListener('click', updateCursorStatus);
-codeEditor.addEventListener('keyup', updateCursorStatus);
+codeEditor.addEventListener('click', () => {
+ updateCursorStatus();
+ updateLinkSuggestions();
+});
+codeEditor.addEventListener('keyup', () => {
+ updateCursorStatus();
+ updateLinkSuggestions();
+});
codeEditor.addEventListener('scroll', () => {
const tab = getActiveTab();
if (tab) {
tab.scrollTop = codeEditor.scrollTop;
tab.scrollLeft = codeEditor.scrollLeft;
}
+ syncPreviewToEditor();
});
+previewPane.addEventListener('scroll', () => {
+ syncEditorToPreview();
+});
+
+for (const dropTarget of [codeEditor, previewPane]) {
+ dropTarget.addEventListener('dragover', (event) => {
+ event.preventDefault();
+ dropTarget.classList.add('drop-target');
+ });
+ dropTarget.addEventListener('dragleave', () => {
+ dropTarget.classList.remove('drop-target');
+ });
+ dropTarget.addEventListener('drop', async (event) => {
+ event.preventDefault();
+ dropTarget.classList.remove('drop-target');
+ await handleExternalDrop(event);
+ });
+}
+
function onEditorInput() {
const tab = getActiveTab();
if (tab) {
@@ -304,6 +393,7 @@ function onEditorInput() {
renderPreview(codeEditor.value);
updateStatusBar();
updateOutline();
+ updateLinkSuggestions();
scheduleAutoSave();
}
@@ -344,6 +434,7 @@ function showWorkspace(tree) {
resetEditorState();
workspaceNameEl.textContent = tree.name;
renderFileTree(tree.children, fileTreeEl, 0);
+ refreshGitStatus();
}
// ── Tab management ────────────────────────────────────────────────────────────
@@ -505,6 +596,8 @@ function renderFileTree(entries, container, depth) {
file.style.paddingLeft = `${12 + depth * 14}px`;
file.textContent = entry.name;
file.dataset.path = entry.path;
+ const gitClass = gitStatusClassForPath(entry.path);
+ if (gitClass) file.classList.add(gitClass);
file.addEventListener('click', () => openFile(entry));
file.addEventListener('contextmenu', (e) => {
e.preventDefault();
@@ -579,6 +672,7 @@ async function promptNewFile(parentDir) {
if (tree) {
workspaceTree = tree;
renderFileTree(tree.children, fileTreeEl, 0);
+ refreshGitStatus();
}
openFile(entry);
} catch (err) {
@@ -597,6 +691,7 @@ async function promptNewFolder(parentDir) {
if (tree) {
workspaceTree = tree;
renderFileTree(tree.children, fileTreeEl, 0);
+ refreshGitStatus();
}
} catch (err) {
window.alert(err.message);
@@ -620,6 +715,7 @@ async function promptRename(entry) {
if (tree) {
workspaceTree = tree;
renderFileTree(tree.children, fileTreeEl, 0);
+ refreshGitStatus();
}
} catch (err) {
window.alert(err.message);
@@ -644,6 +740,7 @@ async function deleteEntry(entry) {
if (tree) {
workspaceTree = tree;
renderFileTree(tree.children, fileTreeEl, 0);
+ refreshGitStatus();
}
} catch (err) {
window.alert(err.message);
@@ -717,6 +814,7 @@ async function saveTab(tabId) {
}
await api.writeFile(tab.path, tab.content);
setDirtyState(tab.id, false);
+ refreshGitStatus();
return true;
} catch (err) {
console.error('Save failed:', err);
@@ -1127,7 +1225,8 @@ document.addEventListener('mouseup', () => {
async function exportCurrentHtml() {
const tab = getActiveTab();
if (!tab) return;
- const htmlBody = markdownToHtml(tab.content);
+ await renderMermaidDiagrams();
+ const htmlBody = previewEl.innerHTML;
const fullHtml = `
@@ -1158,6 +1257,338 @@ async function exportCurrentHtml() {
await api.exportHtml(fullHtml, suggestedName);
}
+async function exportCurrentPdf() {
+ const tab = getActiveTab();
+ if (!tab) return;
+ await renderMermaidDiagrams();
+ const htmlBody = previewEl.innerHTML;
+ const suggestedName = tab.name.replace(/\.md$/i, '.pdf');
+ await api.exportPdf(htmlBody, suggestedName);
+}
+
+async function refreshGitStatus() {
+ if (!workspaceRoot || !api?.getGitStatus) {
+ gitStatusMap = new Map();
+ return;
+ }
+
+ try {
+ const result = await api.getGitStatus();
+ const next = new Map();
+ if (result?.available) {
+ for (const entry of result.entries || []) {
+ next.set(normalizeFsPath(entry.path), entry.status.trim() || '?');
+ }
+ }
+ gitStatusMap = next;
+ if (workspaceTree) renderFileTree(workspaceTree.children, fileTreeEl, 0);
+ } catch (_) {
+ gitStatusMap = new Map();
+ }
+}
+
+function gitStatusClassForPath(filePath) {
+ const status = gitStatusMap.get(normalizeFsPath(filePath));
+ if (!status) return '';
+ if (status.includes('?')) return 'git-untracked';
+ if (status.includes('A')) return 'git-added';
+ if (status.includes('D')) return 'git-deleted';
+ if (/[MRCU]/.test(status)) return 'git-modified';
+ return '';
+}
+
+function toggleDiffOverlay(show) {
+ diffOverlay.hidden = !show;
+}
+
+async function openGitDiff() {
+ const tab = getActiveTab();
+ if (!tab?.path) return;
+
+ const result = await api.getGitDiff(tab.path);
+ diffTitle.textContent = `Git diff • ${tab.name}`;
+
+ const unsavedNote = tab.dirty
+ ? 'Unsaved editor changes are not included in this diff yet. Save the file to compare against Git.\n\n'
+ : '';
+
+ if (!result?.available) {
+ diffOutput.textContent = `${unsavedNote}Git diff is unavailable for this workspace.`;
+ } else if (!result.diff || !result.diff.trim()) {
+ diffOutput.textContent = `${unsavedNote}No tracked Git changes for this file.`;
+ } else {
+ diffOutput.textContent = `${unsavedNote}${result.diff}`;
+ }
+
+ toggleDiffOverlay(true);
+}
+
+function toggleBrokenLinkOverlay(show) {
+ linkCheckOverlay.hidden = !show;
+}
+
+function runBrokenLinkCheck(showOverlay = false) {
+ const tab = getActiveTab();
+ brokenLinkResults = tab?.path ? findBrokenLinks(tab.content, tab.path) : [];
+ renderBrokenLinkStatus();
+ renderBrokenLinkResults();
+ annotatePreviewBrokenLinks();
+ if (showOverlay) toggleBrokenLinkOverlay(true);
+ return brokenLinkResults;
+}
+
+function renderBrokenLinkStatus() {
+ if (!statusEncoding) return;
+ if (brokenLinkResults.length === 0) {
+ statusEncoding.textContent = 'Links: OK';
+ statusEncoding.classList.remove('status-warning');
+ return;
+ }
+ statusEncoding.textContent = `Links: ${brokenLinkResults.length} broken`;
+ statusEncoding.classList.add('status-warning');
+}
+
+function renderBrokenLinkResults() {
+ linkCheckResults.innerHTML = '';
+ if (brokenLinkResults.length === 0) {
+ linkCheckResults.innerHTML = '
No broken relative links found in the active file.
';
+ return;
+ }
+
+ for (const issue of brokenLinkResults) {
+ const item = document.createElement('div');
+ item.className = 'link-issue';
+ item.innerHTML = `
${escapeHtml(issue.target)} Line ${issue.line} • ${escapeHtml(issue.reason)}
`;
+ item.addEventListener('click', () => {
+ toggleBrokenLinkOverlay(false);
+ scrollEditorToLine(issue.line - 1);
+ });
+ linkCheckResults.appendChild(item);
+ }
+}
+
+function annotatePreviewBrokenLinks() {
+ const broken = new Set(brokenLinkResults.map(issue => issue.target));
+ previewEl.querySelectorAll('a[href]').forEach((link) => {
+ const href = link.getAttribute('href') || '';
+ if (broken.has(href)) link.classList.add('broken-link');
+ else link.classList.remove('broken-link');
+ });
+}
+
+function findBrokenLinks(source, currentFilePath) {
+ const entries = collectWorkspaceEntries(workspaceTree?.children || []);
+ const knownPaths = new Set(entries.map(entry => normalizeFsPath(entry.path)));
+ const issues = [];
+ const lines = source.split('\n');
+
+ lines.forEach((lineText, index) => {
+ const regex = /!?\[[^\]]*\]\(([^)]+)\)/g;
+ let match;
+ while ((match = regex.exec(lineText)) !== null) {
+ const rawTarget = match[1].trim().split(/\s+/)[0];
+ if (!isRelativeMarkdownTarget(rawTarget)) continue;
+ const resolved = resolveMarkdownTarget(currentFilePath, rawTarget);
+ if (!resolved || !knownPaths.has(normalizeFsPath(resolved))) {
+ issues.push({ target: rawTarget, line: index + 1, reason: 'Target does not exist in the workspace' });
+ }
+ }
+ });
+
+ return issues;
+}
+
+function isRelativeMarkdownTarget(target) {
+ return target && !/^([a-z]+:|#|mailto:|tel:)/i.test(target);
+}
+
+function resolveMarkdownTarget(currentFilePath, target) {
+ const cleanTarget = target.split('#')[0].split('?')[0];
+ if (!cleanTarget) return null;
+ if (cleanTarget.startsWith('/')) return joinWorkspacePath(workspaceRoot, cleanTarget.slice(1));
+ return joinWorkspacePath(dirnamePath(currentFilePath), cleanTarget);
+}
+
+function updateLinkSuggestions() {
+ const context = getLinkAutocompleteContext();
+ if (!context) {
+ hideLinkSuggestions();
+ return;
+ }
+
+ const files = collectWorkspaceEntries(workspaceTree?.children || []).filter(entry => entry.type === 'file');
+ const suggestions = files
+ .filter(entry => !context.isImage || /\.(png|jpe?g|gif|webp|svg)$/i.test(entry.name))
+ .map(entry => ({
+ entry,
+ relativePath: relativePathBetween(getActiveTab()?.path, entry.path)
+ }))
+ .filter(item => item.relativePath.toLowerCase().includes(context.query.toLowerCase()))
+ .slice(0, 12);
+
+ if (suggestions.length === 0) {
+ hideLinkSuggestions();
+ return;
+ }
+
+ suggestionItems = suggestions;
+ suggestionIndex = 0;
+ suggestionRange = context.range;
+ renderLinkSuggestions();
+}
+
+function getLinkAutocompleteContext() {
+ const beforeCursor = codeEditor.value.slice(0, codeEditor.selectionStart);
+ const match = beforeCursor.match(/(!?\[[^\]]*\]\()([^\n)]*)$/);
+ if (!match) return null;
+ return {
+ isImage: match[1].startsWith('!'),
+ query: match[2],
+ range: {
+ start: codeEditor.selectionStart - match[2].length,
+ end: codeEditor.selectionStart
+ }
+ };
+}
+
+function renderLinkSuggestions() {
+ if (suggestionItems.length === 0) {
+ hideLinkSuggestions();
+ return;
+ }
+
+ linkSuggestions.hidden = false;
+ linkSuggestions.innerHTML = '';
+
+ suggestionItems.forEach((item, index) => {
+ const row = document.createElement('div');
+ row.className = `link-suggestion-item${index === suggestionIndex ? ' active' : ''}`;
+ row.innerHTML = `
${escapeHtml(item.relativePath)} ${escapeHtml(item.entry.name)} `;
+ row.addEventListener('mousedown', (event) => {
+ event.preventDefault();
+ suggestionIndex = index;
+ applyLinkSuggestion();
+ });
+ linkSuggestions.appendChild(row);
+ });
+}
+
+function moveLinkSuggestion(direction) {
+ if (suggestionItems.length === 0) return;
+ suggestionIndex = (suggestionIndex + direction + suggestionItems.length) % suggestionItems.length;
+ renderLinkSuggestions();
+}
+
+function applyLinkSuggestion() {
+ if (!suggestionRange || suggestionItems.length === 0) return;
+ const selected = suggestionItems[suggestionIndex];
+ const before = codeEditor.value.slice(0, suggestionRange.start);
+ const after = codeEditor.value.slice(suggestionRange.end);
+ codeEditor.value = `${before}${selected.relativePath}${after}`;
+ const nextCursor = suggestionRange.start + selected.relativePath.length;
+ codeEditor.selectionStart = codeEditor.selectionEnd = nextCursor;
+ hideLinkSuggestions();
+ onEditorInput();
+ codeEditor.focus();
+}
+
+function hideLinkSuggestions() {
+ suggestionItems = [];
+ suggestionRange = null;
+ linkSuggestions.hidden = true;
+ linkSuggestions.innerHTML = '';
+}
+
+async function handleExternalDrop(event) {
+ const tab = getActiveTab();
+ if (!workspaceRoot || !tab?.path) return;
+
+ const files = Array.from(event.dataTransfer?.files || []).map(file => file.path).filter(Boolean);
+ if (files.length === 0) return;
+
+ const imported = await api.importFilesIntoWorkspace(files, dirnamePath(tab.path));
+ if (!imported || imported.length === 0) return;
+
+ const insertText = imported
+ .map(file => {
+ const relative = relativePathBetween(tab.path, file.path);
+ return file.isImage ? `` : `[${file.name}](${relative})`;
+ })
+ .join('\n');
+
+ insertTextAtCursor(`${insertText}\n`);
+ const tree = await api.refreshTree();
+ if (tree) {
+ workspaceTree = tree;
+ renderFileTree(tree.children, fileTreeEl, 0);
+ refreshGitStatus();
+ }
+}
+
+function insertTextAtCursor(text) {
+ const start = codeEditor.selectionStart;
+ const end = codeEditor.selectionEnd;
+ const value = codeEditor.value;
+ codeEditor.value = value.slice(0, start) + text + value.slice(end);
+ codeEditor.selectionStart = codeEditor.selectionEnd = start + text.length;
+ onEditorInput();
+}
+
+function syncPreviewToEditor() {
+ if (previewSyncLock === 'preview') return;
+ previewSyncLock = 'editor';
+ syncScrollPosition(codeEditor, previewPane);
+ queueMicrotask(() => { previewSyncLock = null; });
+}
+
+function syncEditorToPreview() {
+ if (previewSyncLock === 'editor') return;
+ previewSyncLock = 'preview';
+ syncScrollPosition(previewPane, codeEditor);
+ queueMicrotask(() => { previewSyncLock = null; });
+}
+
+function syncScrollPosition(fromEl, toEl) {
+ const fromMax = Math.max(1, fromEl.scrollHeight - fromEl.clientHeight);
+ const toMax = Math.max(1, toEl.scrollHeight - toEl.clientHeight);
+ const ratio = fromEl.scrollTop / fromMax;
+ toEl.scrollTop = ratio * toMax;
+}
+
+async function loadMermaid() {
+ if (globalThis.mermaid) return globalThis.mermaid;
+ if (!mermaidLoaderPromise) {
+ mermaidLoaderPromise = new Promise((resolve, reject) => {
+ const script = document.createElement('script');
+ script.src = new URL('../../node_modules/mermaid/dist/mermaid.min.js', window.location.href).toString();
+ script.onload = () => resolve(globalThis.mermaid);
+ script.onerror = () => reject(new Error('Failed to load Mermaid renderer'));
+ document.head.appendChild(script);
+ });
+ }
+ const mermaid = await mermaidLoaderPromise;
+ mermaid.initialize({ startOnLoad: false, securityLevel: 'strict' });
+ return mermaid;
+}
+
+async function renderMermaidDiagrams() {
+ const blocks = Array.from(previewEl.querySelectorAll('.mermaid-block[data-mermaid]'));
+ if (blocks.length === 0) return;
+
+ try {
+ const mermaid = await loadMermaid();
+ for (const [index, block] of blocks.entries()) {
+ const source = block.dataset.mermaid || '';
+ const { svg } = await mermaid.render(`docfoundry-mermaid-${Date.now()}-${index}`, source);
+ block.innerHTML = svg;
+ }
+ } catch (_) {
+ for (const block of blocks) {
+ block.innerHTML = `
${escapeHtml(block.dataset.mermaid || '')} `;
+ }
+ }
+}
+
// ── Helpers ───────────────────────────────────────────────────────────────────
function highlightActiveFileInTree() {
document.querySelectorAll('.tree-item-file.active').forEach(el => el.classList.remove('active'));
@@ -1173,9 +1604,65 @@ function resetEditorState() {
updateStatusBar();
updateBreadcrumbs();
updateOutline();
+ hideLinkSuggestions();
+ brokenLinkResults = [];
+ renderBrokenLinkStatus();
document.querySelectorAll('.tree-item-file.active').forEach(el => el.classList.remove('active'));
}
function renderPreview(source) {
previewEl.innerHTML = markdownToHtml(source);
+ renderMermaidDiagrams();
+ runBrokenLinkCheck(false);
+}
+
+function collectWorkspaceEntries(entries, items = []) {
+ for (const entry of entries) {
+ items.push(entry);
+ if (entry.type === 'folder' && Array.isArray(entry.children)) {
+ collectWorkspaceEntries(entry.children, items);
+ }
+ }
+ return items;
+}
+
+function normalizeFsPath(input) {
+ return String(input || '').replace(/\\/g, '/').replace(/\/+/g, '/');
+}
+
+function dirnamePath(filePath) {
+ const normalized = normalizeFsPath(filePath);
+ const parts = normalized.split('/');
+ parts.pop();
+ return parts.join('/') || normalized;
+}
+
+function joinWorkspacePath(basePath, relativePath) {
+ const baseParts = normalizeFsPath(basePath).split('/');
+ const relativeParts = normalizeFsPath(relativePath).split('/');
+ const joined = [...baseParts];
+
+ for (const part of relativeParts) {
+ if (!part || part === '.') continue;
+ if (part === '..') joined.pop();
+ else joined.push(part);
+ }
+
+ return joined.join('/');
+}
+
+function relativePathBetween(fromFilePath, toPath) {
+ const root = normalizeFsPath(workspaceRoot || '');
+ const fromDir = normalizeFsPath(dirnamePath(fromFilePath)).replace(root, '').replace(/^\//, '');
+ const toRelative = normalizeFsPath(toPath).replace(root, '').replace(/^\//, '');
+ const fromSegments = fromDir ? fromDir.split('/') : [];
+ const toSegments = toRelative ? toRelative.split('/') : [];
+
+ while (fromSegments.length && toSegments.length && fromSegments[0] === toSegments[0]) {
+ fromSegments.shift();
+ toSegments.shift();
+ }
+
+ const prefix = fromSegments.map(() => '..');
+ return [...prefix, ...toSegments].join('/') || '.';
}
diff --git a/src/renderer/styles.css b/src/renderer/styles.css
index 01cd302..2ba2ad6 100644
--- a/src/renderer/styles.css
+++ b/src/renderer/styles.css
@@ -265,6 +265,23 @@ h1 {
color: #fff;
}
+.tree-item-file.git-modified::after,
+.tree-item-file.git-untracked::after,
+.tree-item-file.git-added::after,
+.tree-item-file.git-deleted::after {
+ content: '';
+ float: right;
+ width: 8px;
+ height: 8px;
+ border-radius: 999px;
+ margin-top: 6px;
+}
+
+.tree-item-file.git-modified::after { background: #f59e0b; }
+.tree-item-file.git-untracked::after { background: #60a5fa; }
+.tree-item-file.git-added::after { background: #34d399; }
+.tree-item-file.git-deleted::after { background: #f87171; }
+
.tree-item-folder {
font-weight: 600;
color: rgba(255, 255, 255, 0.7);
@@ -554,6 +571,7 @@ h1 {
.pane-editor {
border-right: 1px solid rgba(255, 255, 255, 0.06);
+ position: relative;
}
.code-editor {
@@ -575,6 +593,43 @@ h1 {
color: #999;
}
+.link-suggestions {
+ position: absolute;
+ left: 16px;
+ bottom: 16px;
+ width: min(420px, calc(100% - 32px));
+ max-height: 220px;
+ overflow-y: auto;
+ background: #2a2d35;
+ color: #fff;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 12px;
+ box-shadow: 0 20px 60px rgba(0,0,0,0.4);
+ z-index: 20;
+}
+
+.link-suggestion-item {
+ display: flex;
+ justify-content: space-between;
+ gap: 10px;
+ padding: 10px 14px;
+ cursor: pointer;
+}
+
+.link-suggestion-item.active,
+.link-suggestion-item:hover {
+ background: rgba(18, 106, 88, 0.28);
+}
+
+.link-suggestion-path {
+ color: rgba(255, 255, 255, 0.9);
+}
+
+.link-suggestion-meta {
+ color: rgba(255, 255, 255, 0.42);
+ font-size: 0.78rem;
+}
+
/* Preview pane */
.pane-preview {
@@ -607,6 +662,7 @@ h1 {
.preview-content .task-item { list-style: none; margin-left: -22px; }
.preview-content .task-item input { margin-right: 8px; accent-color: var(--accent); }
.preview-content a { color: var(--accent); }
+.preview-content a.broken-link { color: #b42318; text-decoration-style: wavy; }
.preview-content img { border-radius: 8px; margin: 8px 0; }
.preview-content table { width: 100%; border-collapse: collapse; margin: 14px 0; font-size: 0.9rem; }
.preview-content th,
@@ -622,6 +678,21 @@ h1 {
font-style: italic;
}
+.preview-content .mermaid-block {
+ margin: 14px 0;
+ padding: 14px;
+ border-radius: 10px;
+ background: rgba(18, 106, 88, 0.06);
+ border: 1px solid rgba(18, 106, 88, 0.14);
+ overflow-x: auto;
+}
+
+.preview-content .mermaid-fallback {
+ color: var(--muted);
+ font-family: 'JetBrains Mono', monospace;
+ white-space: pre-wrap;
+}
+
/* Status bar */
.status-bar {
@@ -658,6 +729,11 @@ h1 {
opacity: 1;
}
+.status-item.status-warning {
+ background: rgba(255, 255, 255, 0.12);
+ opacity: 1;
+}
+
/* ── Overlays ────────────────────────────────────────────────────────────── */
.overlay-backdrop {
@@ -749,6 +825,53 @@ h1 {
align-self: flex-start;
}
+.panel-title {
+ color: #fff;
+ font-size: 0.9rem;
+ font-weight: 700;
+ padding-left: 14px;
+}
+
+.diff-panel {
+ max-height: 70vh;
+}
+
+.diff-output {
+ margin: 0;
+ padding: 16px 18px 22px;
+ color: #c8cdd3;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.82rem;
+ line-height: 1.55;
+ overflow: auto;
+ white-space: pre-wrap;
+}
+
+.link-check-panel .search-results {
+ padding: 8px 0 16px;
+}
+
+.link-issue {
+ padding: 12px 18px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.06);
+ color: #c8cdd3;
+ cursor: pointer;
+}
+
+.link-issue:hover {
+ background: rgba(18, 106, 88, 0.18);
+}
+
+.link-issue strong {
+ display: block;
+ color: #fff;
+}
+
+.link-issue-meta {
+ color: rgba(255, 255, 255, 0.45);
+ font-size: 0.78rem;
+}
+
.search-panel-header {
display: flex;
align-items: center;
diff --git a/tests/markdown.test.mjs b/tests/markdown.test.mjs
index dc5fc8e..f259e44 100644
--- a/tests/markdown.test.mjs
+++ b/tests/markdown.test.mjs
@@ -62,6 +62,13 @@ describe('markdownToHtml', () => {
expect(html).toContain('console.log');
});
+ it('renders Mermaid code fences as diagram placeholders', () => {
+ const md = '```mermaid\ngraph TD\nA-->B\n```';
+ const html = markdownToHtml(md);
+ expect(html).toContain('class="mermaid-block"');
+ expect(html).toContain('data-mermaid=');
+ });
+
it('renders inline code', () => {
const html = markdownToHtml('Use `npm install`');
expect(html).toContain('
npm install');
@@ -136,9 +143,12 @@ describe('markdownToHtml', () => {
it('does not render footnote definitions as paragraphs', () => {
const md = 'Text[^1]\n\n[^1]: Definition here';
const html = markdownToHtml(md);
- const paragraphs = html.match(/
/g) || [];
- // Should have 1 paragraph for "Text[^1]", not 2
- expect(paragraphs.length).toBe(1);
+ const [body] = html.split('