diff --git a/.gitignore b/.gitignore index d1a8f7cf9d..cb8e2a5e97 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,10 @@ __pycache__ .DS_Store Thumbs.db +# Vitest +__screenshots__/ +/.vitest-attachments/ + # Playwright /playwright/results/ /playwright/.cache/ diff --git a/api-goldens/element-ng/markdown/index.api.md b/api-goldens/element-ng/markdown/index.api.md new file mode 100644 index 0000000000..873b41497f --- /dev/null +++ b/api-goldens/element-ng/markdown/index.api.md @@ -0,0 +1,123 @@ +## Public API Report File for "@siemens/element-ng_markdown" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import * as _angular_core from '@angular/core'; +import * as _angular_platform_browser from '@angular/platform-browser'; +import { InputSignal } from '@angular/core'; +import * as mdast from 'mdast'; +import { Node as Node_2 } from 'mdast'; +import { Parent } from 'mdast'; +import { PluggableList } from 'unified'; +import { Plugin as Plugin_2 } from 'unified'; +import { Preset } from 'unified'; +import { Processor } from 'unified'; +import { Root } from 'mdast'; +import { Signal } from '@angular/core'; +import { TemplateRef } from '@angular/core'; +import { Transformer as Transformer_2 } from 'unified'; +import { Type } from '@angular/core'; + +// @public +export interface ComponentWithOptions { + component: Type; + options?: any; +} + +// @public (undocumented) +export const makeSiMarkdownOptions: () => SiMarkdownOptions; + +// @public +export interface PluginWithOptions { + // (undocumented) + options?: any; + // (undocumented) + plugin: UnifiedPlugin; +} + +// @public +export class SiMarkdownComponent implements SiMarkdownControl { + readonly debug: _angular_core.InputSignal; + readonly markdown: _angular_core.InputSignal; + readonly options: _angular_core.InputSignal; +} + +// @public +export interface SiMarkdownExtension { + codeTypes?: TypeHandler[]; + plugins?: PluginWithOptions[]; + types?: TypeHandler[]; +} + +// @public +export interface SiMarkdownExtensionComponent { + node: InputSignal; + // (undocumented) + options: InputSignal; + parent: InputSignal; +} + +// @public +export class SiMarkdownFragmentComponent { + readonly children: _angular_core.Signal; + readonly node: _angular_core.InputSignal; +} + +// @public (undocumented) +export type SiMarkdownHighlighter = ComponentWithOptions; + +// @public +export interface SiMarkdownHighlighterComponent { + code: InputSignal; + language: InputSignal; + // (undocumented) + options: InputSignal; + updateLanguage: InputSignal<(lang?: string) => void>; +} + +// @public (undocumented) +export class SiMarkdownInlineHtmlComponent implements SiMarkdownExtensionComponent { + // (undocumented) + readonly node: _angular_core.InputSignal; + // (undocumented) + readonly options: _angular_core.InputSignal; + // (undocumented) + readonly parent: _angular_core.InputSignal; +} + +// @public +export class SiMarkdownOptions { + constructor(); + // (undocumented) + getCodeTypeHandlers(): Map; + // (undocumented) + getHighlighter(): SiMarkdownHighlighter | undefined; + // (undocumented) + getTypeHandlers(): TypeHandler[]; + installExtension(extension: SiMarkdownExtension): SiMarkdownOptions; + installUnifiedPlugin(plugin: UnifiedPlugin, options?: any): SiMarkdownOptions; + makeProcessor(): Processor; + setCodeHighlighter(highlighter?: SiMarkdownHighlighter): SiMarkdownOptions; +} + +// @public +export class SiMarkdownTemplateDirective { + // (undocumented) + readonly nodeType: _angular_core.InputSignal; + // (undocumented) + readonly template: TemplateRef; +} + +// @public +export interface TypeHandler extends ComponentWithOptions { + type: string; +} + +// @public (undocumented) +export type UnifiedPlugin = Plugin_2 | Transformer_2 | Preset | PluggableList; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/docs/components/chat-messages/markdown.md b/docs/components/chat-messages/markdown.md new file mode 100644 index 0000000000..5e7575c342 --- /dev/null +++ b/docs/components/chat-messages/markdown.md @@ -0,0 +1,126 @@ +# Markdown + +> **Note:** The markdown component is currently experimental and may undergo changes in future releases. + +The **markdown** component renders Markdown content as Angular components. Use its options to configure extensions, code highlighting, and custom rendering behavior. Options can be shared between multiple component instances. + +## Usage --- + +Create the options once and pass the same instance to each `si-markdown` component that needs the configuration. The component supports GitHub Flavored Markdown by default, including tables, task lists, strikethrough, and autolinked URLs. + +```ts +import { Component, signal } from '@angular/core'; +import { makeSiMarkdownOptions, SiMarkdownComponent } from '@siemens/element-ng/markdown'; +import { siMarkdownMathKaTeX } from '@siemens/element-ng/markdown/extensions/katex'; +import { siMarkdownMermaid } from '@siemens/element-ng/markdown/extensions/mermaid'; +import { siMarkdownHighlightJs } from '@siemens/element-ng/markdown/hightlighter/highlightjs'; +import remarkGemoji from 'remark-gemoji'; + +@Component({ + imports: [SiMarkdownComponent], + template: ` ` +}) +export class MarkdownExampleComponent { + protected readonly markdown = signal('# Release notes'); + protected readonly markdownOptions = makeSiMarkdownOptions() + .setCodeHighlighter(siMarkdownHighlightJs({ autoDetectLanguage: true })) + .installExtension(siMarkdownMathKaTeX()) + .installExtension(siMarkdownMermaid()) + .installUnifiedPlugin(remarkGemoji); +} +``` + +## Extensions --- + +The following rendering support is included in every Markdown component: + +| Rendering support | Purpose | +| ------------------------ | -------------------------------------------------------------- | +| GitHub Flavored Markdown | Parses tables, task lists, strikethrough, and autolinked URLs. | +| Inline HTML | Renders sanitized inline HTML. | +| Code blocks | Renders fenced code blocks without syntax highlighting. | + +Optional integrations are configured through `SiMarkdownOptions`: + +| Integration | Configuration | Purpose | +| ------------------------------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| [KaTeX](https://katex.org/) | `.installExtension(siMarkdownMathKaTeX())` | Parses and renders inline and block LaTeX math expressions. It accepts optional `remark-math` parser and KaTeX rendering options. | +| [Mermaid](https://mermaid.js.org/) | `.installExtension(siMarkdownMermaid())` | Renders fenced code blocks declared as `mermaid` as diagrams. It accepts optional Mermaid configuration. | +| [Highlight.js](https://highlightjs.org/) | `.setCodeHighlighter(siMarkdownHighlightJs())` | Adds syntax highlighting to fenced code blocks. It accepts Highlight.js options, including automatic language detection. | +| [Gemojis](https://github.com/github/gemoji) | `.installUnifiedPlugin(remarkGemoji)` | Converts emoji shortcodes such as `:rocket:` to emoji. | + +You can also add a compatible [unified](https://unifiedjs.com/) or [remark](https://remark.js.org/) plugin with `.installUnifiedPlugin(plugin, options)`. This is useful for syntax that is not covered by the Element integrations, such as emoji shortcodes. + +> **Bundle size:** KaTeX, Mermaid, Highlight.js, and additional `unified` plugins increase the application bundle size. Import and configure only the integrations your Markdown content requires. + +## Custom extension --- + +An extension can install `unified` plugin(s) and associate the AST node types produced by that plugin with Angular renderer components. The KaTeX integration is an example: `remark-math` parses Markdown math into `math` and `inlineMath` nodes, which are then rendered by the same component. + +```ts +import { type KatexOptions } from 'katex'; +import remarkMath, { type Options as RemarkMathOptions } from 'remark-math'; +import { type SiMarkdownExtension } from '@siemens/element-ng/markdown'; + +import { MarkdownKatexComponent } from './markdown-katex.component'; + +export const markdownKaTeX = ( + parseOptions?: RemarkMathOptions, + katexOptions?: KatexOptions +): SiMarkdownExtension => ({ + plugins: [{ plugin: remarkMath, options: parseOptions }], + types: [ + { type: 'math', component: MarkdownKatexComponent, options: katexOptions }, + { type: 'inlineMath', component: MarkdownKatexComponent, options: katexOptions } + ] +}); +``` + +The renderer implements `SiMarkdownExtensionComponent`. Element supplies the parsed node, its parent, and the options provided in the extension definition as signal inputs. + +```ts +import { Component, computed, inject, input } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import { type KatexOptions, renderToString } from 'katex'; +import { type Literal, type Node, type Parent } from 'mdast'; +import { type SiMarkdownExtensionComponent } from '@siemens/element-ng/markdown'; + +@Component({ + selector: 'app-markdown-katex', + template: '', + host: { + '[innerHTML]': 'html()' + } +}) +export class MarkdownKatexComponent implements SiMarkdownExtensionComponent { + private readonly sanitizer = inject(DomSanitizer); + + readonly node = input.required(); + readonly parent = input.required(); + readonly options = input(); + + protected readonly html = computed(() => { + const expression = (this.node() as Literal).value; + return this.sanitizer.bypassSecurityTrustHtml( + renderToString(expression, { + ...this.options(), + displayMode: this.node().type === 'math' + }) + ); + }); +} +``` + +Register the extension with the component options: + +```ts +protected readonly markdownOptions = makeSiMarkdownOptions().installExtension(markdownKaTeX()); +``` + +## Code --- + + + + + + diff --git a/mkdocs.yml b/mkdocs.yml index 19d0643190..b31a6f4921 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -165,6 +165,7 @@ nav: - Chat & messages: - Chat input: 'components/chat-messages/chat-input.md' - Chat message: 'components/chat-messages/chat-message.md' + - Markdown: 'components/chat-messages/markdown.md' - Charts: - Overview: 'components/charts/index.md' - Bar chart: 'components/charts/bar-chart.md' diff --git a/package.json b/package.json index bf1254ce7d..9a4b23b923 100644 --- a/package.json +++ b/package.json @@ -103,12 +103,20 @@ "flag-icons": "7.5.0", "google-libphonenumber": "3.2.44", "gridstack": "12.6.0", + "highlight.js": "^11.11.1", + "katex": "^0.17.0", + "mermaid": "^11.16.0", "ngx-image-cropper": "9.1.6", "ol": "10.9.0", "ol-ext": "4.0.38", "ol-mapbox-style": "13.4.1", + "remark-gemoji": "^8.0.0", + "remark-gfm": "^4.0.1", + "remark-math": "^6.0.0", + "remark-parse": "^11.0.0", "rxjs": "7.8.2", - "tslib": "2.8.1" + "tslib": "2.8.1", + "unified": "^11.0.5" }, "devDependencies": { "@angular-builders/custom-esbuild": "22.0.1", diff --git a/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-ai-message-element-examples-chromium-dark-linux.png b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-ai-message-element-examples-chromium-dark-linux.png index 93e64aba3f..6e304fe6ec 100644 --- a/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-ai-message-element-examples-chromium-dark-linux.png +++ b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-ai-message-element-examples-chromium-dark-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef537fcdb4bdf2010c69e492639696ffb7a5c6b90268cedf3eb1a03a8b091650 -size 14065 +oid sha256:185a84450285e05bb854db192db41afa3d3a703c6f3213beec65dcc40dbbcc89 +size 21737 diff --git a/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-ai-message-element-examples-chromium-light-linux.png b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-ai-message-element-examples-chromium-light-linux.png index bb5390101b..54ffaaac88 100644 --- a/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-ai-message-element-examples-chromium-light-linux.png +++ b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-ai-message-element-examples-chromium-light-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d658adc4170aab7747ccfc062501489d14a49da294f3589744555a18f28168a -size 13747 +oid sha256:49469df358bf9331ea5de845f209ab685eb6b983d5ec34b77d08966489a7b83d +size 21311 diff --git a/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-ai-message.yaml b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-ai-message.yaml index b49b97c2b4..b47df4565c 100644 --- a/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-ai-message.yaml +++ b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-ai-message.yaml @@ -1,7 +1,8 @@ - paragraph: - text: Here's a - strong: simple response - - text: with basic formatting. + - text: with basic formatting and source citations. + - 'button "View source: Connected Devices Guide"' - paragraph: - text: You can use - code: inline code @@ -9,6 +10,9 @@ - list: - listitem: First item - listitem: Second item +- paragraph: + - text: Production installations should follow the device and configuration guidance. + - 'button "View sources: Connected Devices Guide and 1 additional source"' - group: - button "Good response" - button "Bad response" diff --git a/playwright/snapshots/static.spec.ts-snapshots/si-markdown-renderer--si-markdown-renderer-element-examples-chromium-dark-linux.png b/playwright/snapshots/static.spec.ts-snapshots/si-markdown-renderer--si-markdown-renderer-element-examples-chromium-dark-linux.png index 5ce87f7b9d..e2e72f924e 100644 --- a/playwright/snapshots/static.spec.ts-snapshots/si-markdown-renderer--si-markdown-renderer-element-examples-chromium-dark-linux.png +++ b/playwright/snapshots/static.spec.ts-snapshots/si-markdown-renderer--si-markdown-renderer-element-examples-chromium-dark-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9099595741c83cc68b109c3e10c580a16baa955edada83be763a4e0463c4b562 -size 142664 +oid sha256:204ca11a4524eb1b9f8f824847462dbcfdfe58d9ce3f32a94b30eb683996e325 +size 129078 diff --git a/playwright/snapshots/static.spec.ts-snapshots/si-markdown-renderer--si-markdown-renderer-element-examples-chromium-light-linux.png b/playwright/snapshots/static.spec.ts-snapshots/si-markdown-renderer--si-markdown-renderer-element-examples-chromium-light-linux.png index 604de4d868..79c3febd5e 100644 --- a/playwright/snapshots/static.spec.ts-snapshots/si-markdown-renderer--si-markdown-renderer-element-examples-chromium-light-linux.png +++ b/playwright/snapshots/static.spec.ts-snapshots/si-markdown-renderer--si-markdown-renderer-element-examples-chromium-light-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc2d2cd0d352ea4b212b3a2fe92921e49a29df13bf23e7560e248301c282fa54 -size 139173 +oid sha256:4814af5ccf3dc0cca394154c0b7fb0cff3e0e43d9508405b1abd8968b220c974 +size 127355 diff --git a/playwright/snapshots/static.spec.ts-snapshots/si-markdown-renderer--si-markdown-renderer.yaml b/playwright/snapshots/static.spec.ts-snapshots/si-markdown-renderer--si-markdown-renderer.yaml index caf1199cb2..ddc27a6817 100644 --- a/playwright/snapshots/static.spec.ts-snapshots/si-markdown-renderer--si-markdown-renderer.yaml +++ b/playwright/snapshots/static.spec.ts-snapshots/si-markdown-renderer--si-markdown-renderer.yaml @@ -1,21 +1,15 @@ - heading "AI Assistant Response" [level=2]: - strong: AI Assistant Response -- paragraph: - - text: Here's a - - strong: comprehensive example - - text: of markdown content with various formatting options. -- paragraph: This is a separate paragraph that demonstrates how double line breaks create proper paragraph spacing. +- text: Here's a +- strong: comprehensive example +- text: of markdown content with various formatting options. This is a separate paragraph that demonstrates how double line breaks create proper paragraph spacing. - heading "Code Examples" [level=2] -- paragraph: - - text: You can use inline code like - - code: console.log('Hello World') - - text: "or multi-line code blocks:" -- paragraph +- text: You can use inline code like +- code: console.log('Hello World') +- text: "or multi-line code blocks:" - code: "function calculateSum(a, b) { return a + b; } const result = calculateSum(5, 3); console.log(`Result: ${result}`);" -- paragraph - heading "Formatting Options" [level=2] -- paragraph: Here's a paragraph explaining the formatting options available. -- paragraph: "Another paragraph with different formatting elements:" +- text: "Here's a paragraph explaining the formatting options available. Another paragraph with different formatting elements:" - list: - listitem: - strong: Bold text @@ -27,117 +21,55 @@ - code: inline code - text: for technical terms - heading "Links" [level=2] -- paragraph: - - text: You can include links such as - - link "Element": - - /url: https://element.siemens.io - - text: for more information, as well as local links like - - link "Internal Page": - - /url: /#/internal-page - - text: . -- paragraph: - - text: "Links are also automatically detected:" - - link "https://angular.io": - - /url: https://angular.io +- text: You can include links such as +- link "Element": + - /url: https://element.siemens.io +- text: for more information, as well as local links like +- link "Internal Page": + - /url: /#/internal-page +- text: ". Links are also automatically detected:" +- link "https://angular.io": + - /url: "https://angular.io\n" +- text: ; - heading "Lists and Bullets" [level=2] -- paragraph: "Here are the key features:" +- text: "Here are the key features:" - list: - listitem: Multi-line code blocks with syntax preservation - listitem: Bold and italic text formatting - listitem: Inline code highlighting +- list: - listitem: Bullet point lists - listitem: Blockquote support -- paragraph: This paragraph appears after the list to show proper spacing. +- text: This paragraph appears after the list to show proper spacing. - heading "Ordered Lists" [level=2] -- paragraph: "Step-by-step instructions:" +- text: "Step-by-step instructions:" - list: - listitem: First, analyze the requirements - listitem: Then, implement the solution - listitem: Finally, test the implementation - blockquote: This is a blockquote that demonstrates how quoted text appears in the markdown content component. -- paragraph: This paragraph follows the blockquotes to demonstrate proper paragraph separation. -- paragraph: This is a separate paragraph created by double line breaks. +- text: This paragraph follows the blockquotes to demonstrate proper paragraph separation. This is a separate paragraph created by double line breaks. - list: - listitem: Unordered item - listitem: Another item -- paragraph: Another paragraph between the lists. +- text: Another paragraph between the lists. - list: - listitem: First ordered item - listitem: Second ordered item -- paragraph: Final paragraph to show proper spacing. +- text: Final paragraph to show proper spacing. - heading "Tables" [level=2] -- paragraph: "Tables are also supported:" -- paragraph -- table: - - rowgroup: - - row "Feature Examples Status Notes": - - cell "Feature": - - paragraph: Feature - - cell "Examples": - - paragraph: Examples - - cell "Status": - - paragraph: Status - - cell "Notes": - - paragraph: Notes - - row "Basic content Alice Johnson Bob Smith ✓ Complete Simple text and line breaks": - - cell "Basic content": - - paragraph: - - strong: Basic content - - cell "Alice Johnson Bob Smith": - - paragraph: Alice Johnson Bob Smith - - cell "✓ Complete": - - paragraph: ✓ Complete - - cell "Simple text and line breaks": - - paragraph: Simple text and line breaks - - row "Formatting Bold and italic Plus code and multiline code ✓ Complete Multiple markdown formats": - - cell "Formatting": - - paragraph: - - emphasis: Formatting - - cell "Bold and italic Plus code and multiline code": - - paragraph: - - strong: Bold - - text: and - - emphasis: italic - - text: Plus - - code: code - - text: and - - code: multiline code - - cell "✓ Complete": - - paragraph: ✓ Complete - - cell "Multiple markdown formats": - - paragraph: Multiple markdown formats - - row "Lists in cells First item Second item Third item ✓ Complete Bullet lists work properly": - - cell "Lists in cells": - - paragraph: Lists in cells - - cell "First item Second item Third item": - - list: - - listitem: First item - - listitem: Second item - - listitem: Third item - - cell "✓ Complete": - - paragraph: ✓ Complete - - cell "Bullet lists work properly": - - paragraph: Bullet lists work properly - - 'row "Escaped pipes grep \"text|pattern\" awk ''{print $1|$2}'' ✓ Complete Use | for literal pipes"': - - cell "Escaped pipes": - - paragraph: Escaped pipes - - 'cell "grep \"text|pattern\" awk ''{print $1|$2}''"': - - paragraph: "grep \"text|pattern\" awk '{print $1|$2}'" - - cell "✓ Complete": - - paragraph: ✓ Complete - - cell "Use | for literal pipes": - - paragraph: Use | for literal pipes - - row "Line breaks Line 1 Line 2 Line 3 ✓ Complete Uses
tags": - - cell "Line breaks": - - paragraph: Line breaks - - cell "Line 1 Line 2 Line 3": - - paragraph: Line 1 Line 2 Line 3 - - cell "✓ Complete": - - paragraph: ✓ Complete - - cell "Uses
tags": - - paragraph: - - text: Uses - - code:
- - text: tags -- text: This paragraph appears after the tables to demonstrate proper spacing. -- paragraph \ No newline at end of file +- text: "Tables are also supported: | Feature | Examples | Status | Notes | | ----------------- | ------------------------------------------------------------ | ---------- | --------------------------- | |" +- strong: Basic content +- text: "| Alice Johnson Bob Smith | ✓ Complete | Simple text and line breaks | |" +- emphasis: Formatting +- text: "|" +- strong: Bold +- text: and +- emphasis: italic +- text: Plus +- code: code +- text: and +- code: multiline code +- text: "| ✓ Complete | Multiple markdown formats | | Lists in cells | - First item - Second item - Third item | ✓ Complete | Bullet lists work properly | | Escaped pipes | grep \"text\\|pattern\" awk '{print $1\\|$2}' | ✓ Complete | Use \\| for literal pipes | | Line breaks | Line 1 Line 2 Line 3 | ✓ Complete | Uses" +- code:
+- text: tags | This paragraph appears after the tables to demonstrate proper spacing. \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 77f42d0553..5ebaff6a30 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,6 +100,15 @@ importers: gridstack: specifier: 12.6.0 version: 12.6.0 + highlight.js: + specifier: ^11.11.1 + version: 11.11.1 + katex: + specifier: ^0.17.0 + version: 0.17.0 + mermaid: + specifier: ^11.16.0 + version: 11.16.0 ngx-image-cropper: specifier: 9.1.6 version: 9.1.6(@angular/common@22.0.5(@angular/core@22.0.5(@angular/compiler@22.0.5)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.0.5(@angular/compiler@22.0.5)(rxjs@7.8.2)) @@ -112,12 +121,27 @@ importers: ol-mapbox-style: specifier: 13.4.1 version: 13.4.1(ol@10.9.0) + remark-gemoji: + specifier: ^8.0.0 + version: 8.0.0 + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + remark-math: + specifier: ^6.0.0 + version: 6.0.0 + remark-parse: + specifier: ^11.0.0 + version: 11.0.0 rxjs: specifier: 7.8.2 version: 7.8.2 tslib: specifier: 2.8.1 version: 2.8.1 + unified: + specifier: ^11.0.5 + version: 11.0.5 devDependencies: '@angular-builders/custom-esbuild': specifier: 22.0.1 @@ -405,9 +429,30 @@ importers: google-libphonenumber: specifier: ^3.2.40 version: 3.2.44 + highlight.js: + specifier: ^11.11.1 + version: 11.11.1 + katex: + specifier: ^0.17.0 + version: 0.17.0 + mermaid: + specifier: ^11.16.0 + version: 11.16.0 ngx-image-cropper: specifier: ^9.0.0 version: 9.1.6(@angular/common@22.0.5(@angular/core@22.0.5(@angular/compiler@22.0.5)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.0.5(@angular/compiler@22.0.5)(rxjs@7.8.2)) + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + remark-math: + specifier: ^6.0.0 + version: 6.0.0 + remark-parse: + specifier: ^11.0.0 + version: 11.0.0 + unified: + specifier: ^11.0.5 + version: 11.0.5 publishDirectory: ../../dist/@siemens/element-ng projects/element-theme: {} @@ -899,6 +944,9 @@ packages: '@angular/platform-browser': 22.0.5 rxjs: ^6.5.3 || ^7.4.0 + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==, tarball: https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz} + '@axe-core/playwright@4.12.1': resolution: {integrity: sha512-rMd7xriptqKpP+w5265i4Hdkv2X5kbu6uiBi/B2I7uf3hieRBM3qDCfaKPtxfiYb2mKXfF+yLODJwIx+Jv1GDw==, tarball: https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.12.1.tgz} peerDependencies: @@ -1492,12 +1540,18 @@ packages: '@blazediff/core@1.9.1': resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==, tarball: https://registry.npmjs.org/@blazediff/core/-/core-1.9.1.tgz} + '@braintree/sanitize-url@7.1.2': + resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==, tarball: https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz} + '@cacheable/memory@2.2.0': resolution: {integrity: sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==, tarball: https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz} '@cacheable/utils@2.5.0': resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==, tarball: https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz} + '@chevrotain/types@11.1.2': + resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==, tarball: https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz} + '@chialab/cjs-to-esm@0.19.2': resolution: {integrity: sha512-si+dfCzglT+zh+4okOjA+BKnb2SfdmD81pEYW9qgtzWQbHSSai4niz7kd0Q7JtfHSMlttYkYw1TSybl1M7ETpA==, tarball: https://registry.npmjs.org/@chialab/cjs-to-esm/-/cjs-to-esm-0.19.2.tgz} engines: {node: '>=18'} @@ -2060,6 +2114,12 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==, tarball: https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz} engines: {node: '>=18.18'} + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==, tarball: https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz} + + '@iconify/utils@3.1.4': + resolution: {integrity: sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==, tarball: https://registry.npmjs.org/@iconify/utils/-/utils-3.1.4.tgz} + '@inquirer/ansi@2.0.7': resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==, tarball: https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.7.tgz} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} @@ -2415,6 +2475,9 @@ packages: resolution: {integrity: sha512-lichxSiagMEBBrqHF0trtMQH9RKh+9jUlIJl0qW0QHvt2H/tbvUWdE+ZzI2Jd0/pT7j/iavLonlPu7EQ/ixTOw==, tarball: https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.10.0.tgz} hasBin: true + '@mermaid-js/parser@1.2.0': + resolution: {integrity: sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==, tarball: https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.2.0.tgz} + '@meteocons/svg-static@0.1.0': resolution: {integrity: sha512-dnuXOkt1W5q+XZbyj54dzjuBcKmsC0eLv51IVaW9kzzvzwnhwv9KgNXHKgpvmdN6cX2stMo+k95iMFbcyM4x5Q==, tarball: https://registry.npmjs.org/@meteocons/svg-static/-/svg-static-0.1.0.tgz} @@ -3829,6 +3892,102 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==, tarball: https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==, tarball: https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==, tarball: https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==, tarball: https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==, tarball: https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==, tarball: https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==, tarball: https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==, tarball: https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==, tarball: https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==, tarball: https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==, tarball: https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==, tarball: https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==, tarball: https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==, tarball: https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==, tarball: https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==, tarball: https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==, tarball: https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==, tarball: https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==, tarball: https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==, tarball: https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==, tarball: https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz} + + '@types/d3-random@3.0.4': + resolution: {integrity: sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==, tarball: https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.4.tgz} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==, tarball: https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==, tarball: https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==, tarball: https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==, tarball: https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==, tarball: https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==, tarball: https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==, tarball: https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==, tarball: https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==, tarball: https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==, tarball: https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==, tarball: https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==, tarball: https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz} @@ -3871,9 +4030,18 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, tarball: https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz} + '@types/katex@0.16.8': + resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==, tarball: https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==, tarball: https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz} + '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==, tarball: https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz} + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==, tarball: https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz} + '@types/node@24.13.2': resolution: {integrity: sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==, tarball: https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz} @@ -3921,6 +4089,9 @@ packages: '@types/sockjs@0.3.36': resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==, tarball: https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==, tarball: https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==, tarball: https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz} @@ -4060,6 +4231,9 @@ packages: resolution: {integrity: sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==, tarball: https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@upsetjs/venn.js@2.0.0': + resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==, tarball: https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz} + '@vitejs/plugin-basic-ssl@2.3.0': resolution: {integrity: sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ==, tarball: https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.3.0.tgz} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -4541,6 +4715,9 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==, tarball: https://registry.npmjs.org/bail/-/bail-2.0.2.tgz} + balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==, tarball: https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz} engines: {node: 18 || 20 || >=22} @@ -4653,6 +4830,9 @@ packages: caniuse-lite@1.0.30001805: resolution: {integrity: sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==, tarball: https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==, tarball: https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==, tarball: https://registry.npmjs.org/chai/-/chai-6.2.2.tgz} engines: {node: '>=18'} @@ -4673,6 +4853,9 @@ packages: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==, tarball: https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz} engines: {node: '>=10'} + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==, tarball: https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz} + chardet@2.2.0: resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==, tarball: https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz} @@ -4782,6 +4965,14 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==, tarball: https://registry.npmjs.org/commander/-/commander-2.20.3.tgz} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==, tarball: https://registry.npmjs.org/commander/-/commander-7.2.0.tgz} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==, tarball: https://registry.npmjs.org/commander/-/commander-8.3.0.tgz} + engines: {node: '>= 12'} + common-path-prefix@3.0.0: resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==, tarball: https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz} @@ -5070,6 +5261,12 @@ packages: resolution: {integrity: sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==, tarball: https://registry.npmjs.org/corser/-/corser-2.0.1.tgz} engines: {node: '>= 0.4.0'} + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==, tarball: https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==, tarball: https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz} + cosmiconfig-typescript-loader@6.3.0: resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==, tarball: https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.3.0.tgz} engines: {node: '>=v18'} @@ -5148,10 +5345,169 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==, tarball: https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz} + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==, tarball: https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==, tarball: https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.34.0: + resolution: {integrity: sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==, tarball: https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.0.tgz} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==, tarball: https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==, tarball: https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==, tarball: https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==, tarball: https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==, tarball: https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==, tarball: https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==, tarball: https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==, tarball: https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==, tarball: https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==, tarball: https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==, tarball: https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==, tarball: https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==, tarball: https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==, tarball: https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==, tarball: https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==, tarball: https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==, tarball: https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==, tarball: https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==, tarball: https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==, tarball: https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==, tarball: https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==, tarball: https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==, tarball: https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==, tarball: https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==, tarball: https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==, tarball: https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==, tarball: https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==, tarball: https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==, tarball: https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==, tarball: https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==, tarball: https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==, tarball: https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==, tarball: https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==, tarball: https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==, tarball: https://registry.npmjs.org/d3/-/d3-7.9.0.tgz} + engines: {node: '>=12'} + + dagre-d3-es@7.0.14: + resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==, tarball: https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz} + data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==, tarball: https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz} engines: {node: '>= 12'} + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==, tarball: https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz} + de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==, tarball: https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz} @@ -5172,6 +5528,9 @@ packages: supports-color: optional: true + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==, tarball: https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz} + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==, tarball: https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz} engines: {node: '>=4.0.0'} @@ -5191,6 +5550,9 @@ packages: resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==, tarball: https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz} engines: {node: '>=12'} + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==, tarball: https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz} + depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==, tarball: https://registry.npmjs.org/depd/-/depd-1.1.2.tgz} engines: {node: '>= 0.6'} @@ -5203,6 +5565,10 @@ packages: resolution: {integrity: sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==, tarball: https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz} engines: {node: '>=4'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==, tarball: https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz} + engines: {node: '>=6'} + destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==, tarball: https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -5219,6 +5585,9 @@ packages: detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==, tarball: https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==, tarball: https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz} + diff@8.0.4: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==, tarball: https://registry.npmjs.org/diff/-/diff-8.0.4.tgz} engines: {node: '>=0.3.1'} @@ -5241,6 +5610,9 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==, tarball: https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz} engines: {node: '>= 4'} + dompurify@3.4.12: + resolution: {integrity: sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==, tarball: https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz} + domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==, tarball: https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz} @@ -5516,6 +5888,9 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==, tarball: https://registry.npmjs.org/express/-/express-5.2.1.tgz} engines: {node: '>= 18'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==, tarball: https://registry.npmjs.org/extend/-/extend-3.0.2.tgz} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, tarball: https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz} @@ -5704,6 +6079,9 @@ packages: resolution: {integrity: sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==, tarball: https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz} engines: {node: '>=18'} + gemoji@8.1.0: + resolution: {integrity: sha512-HA4Gx59dw2+tn+UAa7XEV4ufUKI4fH1KgcbenVA9YKSj1QJTT0xh5Mwv5HMFNN3l2OtUe3ZIfuRwSyZS5pLIWw==, tarball: https://registry.npmjs.org/gemoji/-/gemoji-8.1.0.tgz} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, tarball: https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz} engines: {node: '>=6.9.0'} @@ -5811,6 +6189,9 @@ packages: gridstack@12.6.0: resolution: {integrity: sha512-dUrqsormSybFn/2P4Dz8AgprftKD5e/IiV7UmC0XLQU+G+/WtkAeFiCSNLoAGhPDXoJ/O61Xtj3gljY/Ds83yQ==, tarball: https://registry.npmjs.org/gridstack/-/gridstack-12.6.0.tgz} + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==, tarball: https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz} + handle-thing@2.0.1: resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==, tarball: https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz} @@ -5853,6 +6234,10 @@ packages: highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==, tarball: https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz} + highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==, tarball: https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz} + engines: {node: '>=12.0.0'} + homedir-polyfill@1.0.3: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==, tarball: https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz} engines: {node: '>=0.10.0'} @@ -6069,6 +6454,13 @@ packages: injection-js@2.6.1: resolution: {integrity: sha512-dbR5bdhi7TWDoCye9cByZqeg/gAfamm8Vu3G1KZOTYkOif8WkuM8CD0oeDPtZYMzT5YH76JAFB7bkmyY9OJi2A==, tarball: https://registry.npmjs.org/injection-js/-/injection-js-2.6.1.tgz} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==, tarball: https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==, tarball: https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz} + engines: {node: '>=12'} + ip-address@10.2.0: resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==, tarball: https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz} engines: {node: '>= 12'} @@ -6325,12 +6717,23 @@ packages: karma-source-map-support@1.4.0: resolution: {integrity: sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==, tarball: https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz} + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==, tarball: https://registry.npmjs.org/katex/-/katex-0.16.47.tgz} + hasBin: true + + katex@0.17.0: + resolution: {integrity: sha512-Vdw0ATsQ9V+LuegM/BTwQqV/6cTl5lbGcIrU+BCgLxyf6bo38ybOr372tuSIxir3CN720flu1meYR6XzNMwQnw==, tarball: https://registry.npmjs.org/katex/-/katex-0.17.0.tgz} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, tarball: https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz} keyv@5.6.0: resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==, tarball: https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz} + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==, tarball: https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz} + kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==, tarball: https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz} engines: {node: '>=0.10.0'} @@ -6341,6 +6744,12 @@ packages: launch-editor@2.14.1: resolution: {integrity: sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==, tarball: https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz} + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==, tarball: https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==, tarball: https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz} + lerc@3.0.0: resolution: {integrity: sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==, tarball: https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz} @@ -6458,6 +6867,9 @@ packages: long-timeout@0.1.1: resolution: {integrity: sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==, tarball: https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==, tarball: https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz} @@ -6511,6 +6923,9 @@ packages: resolution: {integrity: sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==, tarball: https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz} hasBin: true + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==, tarball: https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz} + marked-terminal@7.3.0: resolution: {integrity: sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==, tarball: https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz} engines: {node: '>=16.0.0'} @@ -6522,6 +6937,11 @@ packages: engines: {node: '>= 18'} hasBin: true + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==, tarball: https://registry.npmjs.org/marked/-/marked-16.4.2.tgz} + engines: {node: '>= 20'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, tarball: https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz} engines: {node: '>= 0.4'} @@ -6529,6 +6949,42 @@ packages: mathml-tag-names@4.0.0: resolution: {integrity: sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ==, tarball: https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-4.0.0.tgz} + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==, tarball: https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==, tarball: https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==, tarball: https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==, tarball: https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==, tarball: https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==, tarball: https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==, tarball: https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==, tarball: https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz} + + mdast-util-math@3.0.0: + resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==, tarball: https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==, tarball: https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==, tarball: https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==, tarball: https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz} + mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==, tarball: https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz} @@ -6573,10 +7029,100 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, tarball: https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz} engines: {node: '>= 8'} + mermaid@11.16.0: + resolution: {integrity: sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA==, tarball: https://registry.npmjs.org/mermaid/-/mermaid-11.16.0.tgz} + methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==, tarball: https://registry.npmjs.org/methods/-/methods-1.1.2.tgz} engines: {node: '>= 0.6'} + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==, tarball: https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==, tarball: https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==, tarball: https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==, tarball: https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==, tarball: https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==, tarball: https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==, tarball: https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==, tarball: https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz} + + micromark-extension-math@3.1.0: + resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==, tarball: https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==, tarball: https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==, tarball: https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==, tarball: https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==, tarball: https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==, tarball: https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==, tarball: https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==, tarball: https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==, tarball: https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==, tarball: https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==, tarball: https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==, tarball: https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==, tarball: https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==, tarball: https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==, tarball: https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==, tarball: https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==, tarball: https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==, tarball: https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==, tarball: https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==, tarball: https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==, tarball: https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, tarball: https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz} engines: {node: '>=8.6'} @@ -7075,6 +7621,9 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==, tarball: https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz} + package-manager-detector@1.7.0: + resolution: {integrity: sha512-xg1eHpwYL/D/HEdWw2goFZP6vV0FH7W+PZ5rFkGjdIDLtxq7EkzBUeT3m+lndYCt8wKbmofUu1MUdMCXkCk9ZQ==, tarball: https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.7.0.tgz} + pacote@21.5.1: resolution: {integrity: sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg==, tarball: https://registry.npmjs.org/pacote/-/pacote-21.5.1.tgz} engines: {node: ^20.17.0 || >=22.9.0} @@ -7139,6 +7688,9 @@ packages: path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==, tarball: https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz} + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==, tarball: https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz} + path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==, tarball: https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz} engines: {node: '>=4'} @@ -7246,6 +7798,12 @@ packages: resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==, tarball: https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz} engines: {node: '>=14.19.0'} + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==, tarball: https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==, tarball: https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz} + portfinder@1.0.38: resolution: {integrity: sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==, tarball: https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz} engines: {node: '>= 10.12'} @@ -7524,6 +8082,21 @@ packages: resolution: {integrity: sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==, tarball: https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz} hasBin: true + remark-gemoji@8.0.0: + resolution: {integrity: sha512-/fL9rc72FYwFGtOKcT+QeQdx9Q9t5v4N6KLXSDOTEgaedzK85I9judBqB2eqz+g4b0ERMejlwSOuPK+wket6aA==, tarball: https://registry.npmjs.org/remark-gemoji/-/remark-gemoji-8.0.0.tgz} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==, tarball: https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz} + + remark-math@6.0.0: + resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==, tarball: https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==, tarball: https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==, tarball: https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, tarball: https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz} engines: {node: '>=0.10.0'} @@ -7586,6 +8159,9 @@ packages: engines: {node: 20 || >=22} hasBin: true + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==, tarball: https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz} + rollup-plugin-dts@6.4.1: resolution: {integrity: sha512-l//F3Zf7ID5GoOfLfD8kroBjQKEKpy1qfhtAdnpibFZMffPaylrg1CoDC2vGkPeTeyxUe4bVFCln2EFuL7IGGg==, tarball: https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.4.1.tgz} engines: {node: '>=20'} @@ -7603,6 +8179,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==, tarball: https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==, tarball: https://registry.npmjs.org/router/-/router-2.2.0.tgz} engines: {node: '>= 18'} @@ -7614,6 +8193,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, tarball: https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz} + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==, tarball: https://registry.npmjs.org/rw/-/rw-1.3.3.tgz} + rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==, tarball: https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz} @@ -7986,6 +8568,9 @@ packages: engines: {node: '>=20.19.0'} hasBin: true + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==, tarball: https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz} + super-regex@1.1.0: resolution: {integrity: sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==, tarball: https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz} engines: {node: '>=18'} @@ -8164,12 +8749,19 @@ packages: peerDependencies: tslib: '2' + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==, tarball: https://registry.npmjs.org/trough/-/trough-2.2.0.tgz} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==, tarball: https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==, tarball: https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz} + engines: {node: '>=6.10'} + ts-morph@28.0.0: resolution: {integrity: sha512-Wp3tnZ2bzwxyTZMtgWVzXDfm7lB1Drz+y9DmmYH/L702PQhPyVrp3pkou3yIz4qjS14GY9kcpmLiOOMvl8oG1g==, tarball: https://registry.npmjs.org/ts-morph/-/ts-morph-28.0.0.tgz} @@ -8300,6 +8892,9 @@ packages: resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==, tarball: https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz} engines: {node: '>=20'} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==, tarball: https://registry.npmjs.org/unified/-/unified-11.0.5.tgz} + union@0.5.0: resolution: {integrity: sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==, tarball: https://registry.npmjs.org/union/-/union-0.5.0.tgz} engines: {node: '>= 0.8.0'} @@ -8308,6 +8903,21 @@ packages: resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==, tarball: https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz} engines: {node: '>=12'} + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==, tarball: https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==, tarball: https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==, tarball: https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==, tarball: https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==, tarball: https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz} + universal-user-agent@7.0.3: resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==, tarball: https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz} @@ -8353,6 +8963,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==, tarball: https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz} engines: {node: '>= 0.4.0'} + uuid@14.0.1: + resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==, tarball: https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz} + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==, tarball: https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz} deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). @@ -8369,6 +8983,12 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==, tarball: https://registry.npmjs.org/vary/-/vary-1.1.2.tgz} engines: {node: '>= 0.8'} + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==, tarball: https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==, tarball: https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz} + vite@7.3.5: resolution: {integrity: sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==, tarball: https://registry.npmjs.org/vite/-/vite-7.3.5.tgz} engines: {node: ^20.19.0 || >=22.12.0} @@ -8705,6 +9325,9 @@ packages: zstddec@0.2.0: resolution: {integrity: sha512-oyPnDa1X5c13+Y7mA/FDMNJrn4S8UNBe0KCqtDmor40Re7ALrPN6npFwyYVRRh+PqozZQdeg23QtbcamZnG5rA==, tarball: https://registry.npmjs.org/zstddec/-/zstddec-0.2.0.tgz} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==, tarball: https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz} + snapshots: '@actions/core@3.0.1': @@ -9421,6 +10044,11 @@ snapshots: rxjs: 7.8.2 tslib: 2.8.1 + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.7.0 + tinyexec: 1.2.4 + '@axe-core/playwright@4.12.1(playwright-core@1.61.1)': dependencies: axe-core: 4.12.1 @@ -10244,6 +10872,8 @@ snapshots: '@blazediff/core@1.9.1': {} + '@braintree/sanitize-url@7.1.2': {} + '@cacheable/memory@2.2.0': dependencies: '@cacheable/utils': 2.5.0 @@ -10256,6 +10886,8 @@ snapshots: hashery: 1.5.1 keyv: 5.6.0 + '@chevrotain/types@11.1.2': {} + '@chialab/cjs-to-esm@0.19.2': dependencies: '@chialab/estransform': 0.20.0 @@ -10685,6 +11317,14 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.4': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + import-meta-resolve: 4.2.0 + '@inquirer/ansi@2.0.7': {} '@inquirer/checkbox@5.2.1(@types/node@24.13.2)': @@ -11021,6 +11661,10 @@ snapshots: quickselect: 3.0.0 tinyqueue: 3.0.0 + '@mermaid-js/parser@1.2.0': + dependencies: + '@chevrotain/types': 11.1.2 + '@meteocons/svg-static@0.1.0': {} '@microsoft/api-extractor-model@7.33.8(@types/node@24.13.2)': @@ -12443,46 +13087,166 @@ snapshots: dependencies: '@types/node': 24.13.2 - '@types/deep-eql@4.0.2': {} + '@types/d3-array@3.2.2': {} - '@types/eslint-scope@3.7.7': + '@types/d3-axis@3.0.6': dependencies: - '@types/eslint': 9.6.1 - '@types/estree': 1.0.9 + '@types/d3-selection': 3.0.11 - '@types/eslint@9.6.1': + '@types/d3-brush@3.0.6': dependencies: - '@types/estree': 1.0.9 - '@types/json-schema': 7.0.15 + '@types/d3-selection': 3.0.11 - '@types/esrecurse@4.3.1': {} - - '@types/estree@1.0.8': {} + '@types/d3-chord@3.0.6': {} - '@types/estree@1.0.9': {} + '@types/d3-color@3.1.3': {} - '@types/express-serve-static-core@4.19.9': + '@types/d3-contour@3.0.6': dependencies: - '@types/node': 24.13.2 - '@types/qs': 6.15.1 - '@types/range-parser': 1.2.7 - '@types/send': 1.2.1 + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 - '@types/express@4.17.25': - dependencies: - '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 4.19.9 - '@types/qs': 6.15.1 - '@types/serve-static': 1.15.10 + '@types/d3-delaunay@6.0.4': {} - '@types/geojson@7946.0.16': {} + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.4': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/deep-eql@4.0.2': {} + + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 9.6.1 + '@types/estree': 1.0.9 + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.9 + '@types/json-schema': 7.0.15 + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.8': {} + + '@types/estree@1.0.9': {} + + '@types/express-serve-static-core@4.19.9': + dependencies: + '@types/node': 24.13.2 + '@types/qs': 6.15.1 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@4.17.25': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.9 + '@types/qs': 6.15.1 + '@types/serve-static': 1.15.10 + + '@types/geojson@7946.0.16': {} '@types/google-libphonenumber@7.4.30': {} '@types/hast@3.0.5': dependencies: '@types/unist': 3.0.3 - optional: true '@types/http-errors@2.0.5': {} @@ -12492,8 +13256,16 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/katex@0.16.8': {} + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/mime@1.3.5': {} + '@types/ms@2.1.0': {} + '@types/node@24.13.2': dependencies: undici-types: 7.18.2 @@ -12543,9 +13315,11 @@ snapshots: dependencies: '@types/node': 24.13.2 - '@types/unist@3.0.3': + '@types/trusted-types@2.0.7': optional: true + '@types/unist@3.0.3': {} + '@types/ws@8.18.1': dependencies: '@types/node': 24.13.2 @@ -12743,6 +13517,11 @@ snapshots: '@typescript-eslint/types': 8.64.0 eslint-visitor-keys: 5.0.1 + '@upsetjs/venn.js@2.0.0': + optionalDependencies: + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + '@vitejs/plugin-basic-ssl@2.3.0(vite@7.3.5(@types/node@24.13.2)(jiti@2.7.0)(less@4.6.4)(sass@1.99.0)(terser@5.46.2)(tsx@4.23.0)(yaml@2.9.0))': dependencies: vite: 7.3.5(@types/node@24.13.2)(jiti@2.7.0)(less@4.6.4)(sass@1.99.0)(terser@5.46.2)(tsx@4.23.0)(yaml@2.9.0) @@ -13467,6 +14246,8 @@ snapshots: transitivePeerDependencies: - supports-color + bail@2.0.2: {} + balanced-match@4.0.4: {} baseline-browser-mapping@2.10.43: {} @@ -13606,6 +14387,8 @@ snapshots: caniuse-lite@1.0.30001805: {} + ccount@2.0.1: {} + chai@6.2.2: {} chalk@2.4.2: @@ -13623,6 +14406,8 @@ snapshots: char-regex@1.0.2: {} + character-entities@2.0.2: {} + chardet@2.2.0: {} child_process@1.0.2: @@ -13736,6 +14521,10 @@ snapshots: commander@2.20.3: {} + commander@7.2.0: {} + + commander@8.3.0: {} + common-path-prefix@3.0.0: {} compare-func@2.0.0: @@ -13864,6 +14653,14 @@ snapshots: corser@2.0.1: {} + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + cosmiconfig-typescript-loader@6.3.0(@types/node@24.13.2)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3): dependencies: '@types/node': 24.13.2 @@ -13949,8 +14746,194 @@ snapshots: csstype@3.2.3: {} + cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.0): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.34.0 + + cytoscape-fcose@2.2.0(cytoscape@3.34.0): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.34.0 + + cytoscape@3.34.0: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.1.0 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.14: + dependencies: + d3: 7.9.0 + lodash-es: 4.18.1 + data-uri-to-buffer@4.0.1: {} + dayjs@1.11.21: {} + de-indent@1.0.2: {} debug@2.6.9: @@ -13961,6 +14944,10 @@ snapshots: dependencies: ms: 2.1.3 + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + deep-extend@0.6.0: {} deep-is@0.1.4: {} @@ -13974,12 +14961,18 @@ snapshots: define-lazy-prop@3.0.0: {} + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + depd@1.1.2: {} depd@2.0.0: {} dependency-graph@1.0.0: {} + dequal@2.0.3: {} + destroy@1.2.0: {} detect-libc@1.0.3: {} @@ -13989,6 +14982,10 @@ snapshots: detect-node@2.1.0: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + diff@8.0.4: {} dir-glob@3.0.1: @@ -14011,6 +15008,10 @@ snapshots: dependencies: domelementtype: 2.3.0 + dompurify@3.4.12: + optionalDependencies: + '@types/trusted-types': 2.0.7 + domutils@3.2.2: dependencies: dom-serializer: 2.0.0 @@ -14411,6 +15412,8 @@ snapshots: transitivePeerDependencies: - supports-color + extend@3.0.2: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -14608,6 +15611,8 @@ snapshots: function-timeout@1.0.2: {} + gemoji@8.1.0: {} + gensync@1.0.0-beta.2: {} geotiff@3.0.5: @@ -14734,6 +15739,8 @@ snapshots: gridstack@12.6.0: {} + hachure-fill@0.5.2: {} + handle-thing@2.0.1: {} handlebars@4.7.9: @@ -14767,6 +15774,8 @@ snapshots: highlight.js@10.7.3: {} + highlight.js@11.11.1: {} + homedir-polyfill@1.0.3: dependencies: parse-passwd: 1.0.0 @@ -15000,6 +16009,10 @@ snapshots: dependencies: tslib: 2.8.1 + internmap@1.0.1: {} + + internmap@2.0.3: {} + ip-address@10.2.0: {} ipaddr.js@1.9.1: {} @@ -15185,6 +16198,14 @@ snapshots: dependencies: source-map-support: 0.5.21 + katex@0.16.47: + dependencies: + commander: 8.3.0 + + katex@0.17.0: + dependencies: + commander: 8.3.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -15193,6 +16214,8 @@ snapshots: dependencies: '@keyv/serialize': 1.1.1 + khroma@2.1.0: {} + kind-of@6.0.3: {} known-css-properties@0.37.0: {} @@ -15202,6 +16225,10 @@ snapshots: picocolors: 1.1.1 shell-quote: 1.10.0 + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + lerc@3.0.0: {} less-loader@12.3.2(@rspack/core@2.1.3(@module-federation/runtime-tools@2.6.0(node-fetch@3.3.2)))(less@4.6.4)(webpack@5.106.2(esbuild@0.28.1)(postcss@8.5.13)): @@ -15342,6 +16369,8 @@ snapshots: long-timeout@0.1.1: {} + longest-streak@3.1.0: {} + lru-cache@10.4.3: {} lru-cache@11.5.2: {} @@ -15418,6 +16447,8 @@ snapshots: uc.micro: 2.1.0 optional: true + markdown-table@3.0.4: {} + marked-terminal@7.3.0(marked@15.0.12): dependencies: ansi-escapes: 7.3.0 @@ -15431,10 +16462,126 @@ snapshots: marked@15.0.12: {} + marked@16.4.2: {} + math-intrinsics@1.1.0: {} mathml-tag-names@4.0.0: {} + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-math@3.0.0: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + longest-streak: 3.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + unist-util-remove-position: 5.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdn-data@2.27.1: {} mdurl@2.0.0: @@ -15477,8 +16624,233 @@ snapshots: merge2@1.4.1: {} + mermaid@11.16.0: + dependencies: + '@braintree/sanitize-url': 7.1.2 + '@iconify/utils': 3.1.4 + '@mermaid-js/parser': 1.2.0 + '@types/d3': 7.4.3 + '@upsetjs/venn.js': 2.0.0 + cytoscape: 3.34.0 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.0) + cytoscape-fcose: 2.2.0(cytoscape@3.34.0) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.14 + dayjs: 1.11.21 + dompurify: 3.4.12 + es-toolkit: 1.49.0 + katex: 0.16.47 + khroma: 2.1.0 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.4.0 + ts-dedent: 2.3.0 + uuid: 14.0.1 + methods@1.1.2: {} + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-math@3.1.0: + dependencies: + '@types/katex': 0.16.8 + devlop: 1.1.0 + katex: 0.16.47 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -15961,6 +17333,8 @@ snapshots: package-json-from-dist@1.0.1: {} + package-manager-detector@1.7.0: {} + pacote@21.5.1: dependencies: '@gar/promise-retry': 1.0.3 @@ -16042,6 +17416,8 @@ snapshots: path-browserify@1.0.1: optional: true + path-data-parser@0.1.0: {} + path-exists@3.0.0: {} path-exists@4.0.0: {} @@ -16126,6 +17502,13 @@ snapshots: pngjs@7.0.0: {} + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + portfinder@1.0.38: dependencies: async: 3.2.6 @@ -16392,6 +17775,47 @@ snapshots: dependencies: jsesc: 3.1.0 + remark-gemoji@8.0.0: + dependencies: + '@types/mdast': 4.0.4 + gemoji: 8.1.0 + mdast-util-find-and-replace: 3.0.2 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-math@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-math: 3.0.0 + micromark-extension-math: 3.1.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -16450,6 +17874,8 @@ snapshots: glob: 13.0.6 package-json-from-dist: 1.0.1 + robust-predicates@3.0.3: {} + rollup-plugin-dts@6.4.1(rollup@4.62.2)(typescript@6.0.3): dependencies: '@jridgewell/remapping': 2.3.5 @@ -16523,6 +17949,13 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.62.2 fsevents: 2.3.3 + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + router@2.2.0: dependencies: debug: 4.4.3 @@ -16539,6 +17972,8 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rw@1.3.3: {} + rxjs@7.8.2: dependencies: tslib: 2.8.1 @@ -17026,6 +18461,8 @@ snapshots: - supports-color - typescript + stylis@4.4.0: {} + super-regex@1.1.0: dependencies: function-timeout: 1.0.2 @@ -17175,10 +18612,14 @@ snapshots: dependencies: tslib: 2.8.1 + trough@2.2.0: {} + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: typescript: 6.0.3 + ts-dedent@2.3.0: {} + ts-morph@28.0.0: dependencies: '@ts-morph/common': 0.29.0 @@ -17292,6 +18733,16 @@ snapshots: unicorn-magic@0.4.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + union@0.5.0: dependencies: qs: 6.15.3 @@ -17300,6 +18751,30 @@ snapshots: dependencies: crypto-random-string: 4.0.0 + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + universal-user-agent@7.0.3: {} universalify@2.0.1: {} @@ -17333,6 +18808,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@14.0.1: {} + uuid@8.3.2: {} validate-npm-package-license@3.0.4: @@ -17344,6 +18821,16 @@ snapshots: vary@1.1.2: {} + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + vite@7.3.5(@types/node@24.13.2)(jiti@2.7.0)(less@4.6.4)(sass@1.99.0)(terser@5.46.2)(tsx@4.23.0)(yaml@2.9.0): dependencies: esbuild: 0.27.7 @@ -17864,3 +19351,5 @@ snapshots: tslib: 2.3.0 zstddec@0.2.0: {} + + zwitch@2.0.4: {} diff --git a/projects/element-ng/chat-messages/si-ai-message.component.html b/projects/element-ng/chat-messages/si-ai-message.component.html index 2d2698b7f3..5da12cd759 100644 --- a/projects/element-ng/chat-messages/si-ai-message.component.html +++ b/projects/element-ng/chat-messages/si-ai-message.component.html @@ -6,6 +6,8 @@ } @else {
} + } @else { + } @if (actions().length > 0 || secondaryActions().length > 0) { diff --git a/projects/element-ng/chat-messages/si-ai-message.component.scss b/projects/element-ng/chat-messages/si-ai-message.component.scss index f457b9dbbc..172e617bca 100644 --- a/projects/element-ng/chat-messages/si-ai-message.component.scss +++ b/projects/element-ng/chat-messages/si-ai-message.component.scss @@ -11,6 +11,7 @@ si-chat-message { // Because of the transparent background we need to remove the padding --chat-message-bubble-padding: 0; + line-height: 1.25rem; margin-block-end: -1 * map.get(variables.$spacers, 2); } diff --git a/projects/element-ng/chat-messages/si-user-message.component.html b/projects/element-ng/chat-messages/si-user-message.component.html index 79168a048b..09a0665b3f 100644 --- a/projects/element-ng/chat-messages/si-user-message.component.html +++ b/projects/element-ng/chat-messages/si-user-message.component.html @@ -10,6 +10,8 @@ } @else {
} + } @else { + } @if (actions().length > 0 || secondaryActions().length > 0) {
diff --git a/projects/element-ng/markdown/.editorconfig b/projects/element-ng/markdown/.editorconfig new file mode 100644 index 0000000000..45497ce457 --- /dev/null +++ b/projects/element-ng/markdown/.editorconfig @@ -0,0 +1,2 @@ +[*.spec.ts] +trim_trailing_whitespace = false diff --git a/projects/element-ng/markdown/README.md b/projects/element-ng/markdown/README.md new file mode 100644 index 0000000000..638c399f6c --- /dev/null +++ b/projects/element-ng/markdown/README.md @@ -0,0 +1,8 @@ +## Pluggable markdown rendering component + +This component is built using concepts from [ngx-remark](https://github.com/ericleib/ngx-remark), +namely using Angular templates to render the AST produced by `remark`. + +Unlike `ngx-remark`, it features a configuration based approach of extensibility so that multiple +instances of the component can be used with the same configuration, e.g. inside a chat component +w/o repeating templates. diff --git a/projects/element-ng/markdown/extensions/code/index.ts b/projects/element-ng/markdown/extensions/code/index.ts new file mode 100644 index 0000000000..62ad1d3b89 --- /dev/null +++ b/projects/element-ng/markdown/extensions/code/index.ts @@ -0,0 +1,5 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +export * from './si-markdown-code.extension'; diff --git a/projects/element-ng/markdown/extensions/code/si-markdown-code.component.html b/projects/element-ng/markdown/extensions/code/si-markdown-code.component.html new file mode 100644 index 0000000000..ba81eac07d --- /dev/null +++ b/projects/element-ng/markdown/extensions/code/si-markdown-code.component.html @@ -0,0 +1,33 @@ +
+ + {{ languageToDisplay() }} + +
+ @if (copied()) { +
+ + {{ labelCopied | translate }} +
+ } @else { + + } +
+
+ +@let hl = highlighter(); +@if (hl) { + +} @else { +
{{ code() }}
+} diff --git a/projects/element-ng/markdown/extensions/code/si-markdown-code.component.spec.ts b/projects/element-ng/markdown/extensions/code/si-markdown-code.component.spec.ts new file mode 100644 index 0000000000..a1014a7711 --- /dev/null +++ b/projects/element-ng/markdown/extensions/code/si-markdown-code.component.spec.ts @@ -0,0 +1,55 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SiMarkdownComponent } from '../../si-markdown.component'; + +describe('SiMarkdownCodeComponent', () => { + let fixture: ComponentFixture; + let element: HTMLElement; + + beforeEach(() => { + fixture = TestBed.createComponent(SiMarkdownComponent); + element = fixture.nativeElement; + }); + + it('renders a fenced code block without a highlighter', async () => { + fixture.componentRef.setInput('markdown', '```typescript\nconst answer = 42;\n```'); + fixture.detectChanges(); + await new Promise(resolve => setTimeout(resolve)); + await fixture.whenStable(); + + const codeBlock = element.querySelector('si-markdown-code'); + + expect(codeBlock).not.toBeNull(); + expect(codeBlock).toHaveClass('d-flex', 'flex-column'); + expect(codeBlock?.querySelector('.code-language')).toHaveTextContent('typescript'); + expect(codeBlock?.querySelector('pre code')).toHaveTextContent('const answer = 42;'); + }); + + it('shows a copied confirmation for 1.5 seconds after copying the code block content', async () => { + vi.useFakeTimers(); + const writeText = vi.spyOn(navigator.clipboard, 'writeText').mockResolvedValue(); + fixture.componentRef.setInput('markdown', '```typescript\nconst answer = 42;\n```'); + fixture.detectChanges(); + await vi.runAllTimersAsync(); + await fixture.whenStable(); + + const copyButton = element.querySelector('si-markdown-code button') as HTMLButtonElement; + copyButton.click(); + await Promise.resolve(); + fixture.detectChanges(); + + const copied = element.querySelector('si-markdown-code .copied'); + expect(writeText).toHaveBeenCalledWith('const answer = 42;'); + expect(copied).toHaveTextContent('Copied'); + expect(copied?.querySelector('si-icon')).toHaveAttribute('icon', 'elementOk'); + + await vi.advanceTimersByTimeAsync(1500); + fixture.detectChanges(); + + expect(copyButton).toHaveTextContent('Copy code'); + }); +}); diff --git a/projects/element-ng/markdown/extensions/code/si-markdown-code.component.ts b/projects/element-ng/markdown/extensions/code/si-markdown-code.component.ts new file mode 100644 index 0000000000..46e4abea88 --- /dev/null +++ b/projects/element-ng/markdown/extensions/code/si-markdown-code.component.ts @@ -0,0 +1,51 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { NgComponentOutlet } from '@angular/common'; +import { Component, computed, input, linkedSignal, signal } from '@angular/core'; +import { elementCopy, elementOk } from '@siemens/element-icons'; +import { addIcons, SiIconComponent } from '@siemens/element-ng/icon'; +import { SiTranslatePipe, t } from '@siemens/element-translate-ng/translate'; +import { Code, type Node, type Parent } from 'mdast'; + +import { SiMarkdownHighlighter, SiMarkdownExtensionComponent } from '../../si-markdown.types'; + +export interface MarkdownCodeOptions { + getHighlighter: () => SiMarkdownHighlighter | undefined; +} + +@Component({ + selector: 'si-markdown-code', + imports: [NgComponentOutlet, SiIconComponent, SiTranslatePipe], + templateUrl: './si-markdown-code.component.html', + host: { + class: 'd-flex flex-column px-6 py-5 mb-6 border rounded' + } +}) +export class SiMarkdownCodeComponent implements SiMarkdownExtensionComponent { + readonly node = input.required(); + readonly parent = input.required(); + + readonly options = input(); + + protected labelCopy = t(() => $localize`:@@SI_MARKDOWN.COPY_CODE:Copy code`); + protected labelCopied = t(() => $localize`:@@SI_MARKDOWN.COPIED_CODE:Copied`); + protected readonly code = computed(() => (this.node() as Code).value); + protected readonly language = computed(() => (this.node() as Code).lang); + protected readonly highlighter = computed(() => this.options()?.getHighlighter()); + protected readonly languageToDisplay = linkedSignal(() => this.language()); + protected readonly copied = signal(false); + + protected readonly updateLanguage = (lang?: string): void => this.languageToDisplay.set(lang); + + constructor() { + addIcons({ elementCopy, elementOk }); + } + + protected async copy(): Promise { + await navigator.clipboard.writeText(this.code()); + this.copied.set(true); + setTimeout(() => this.copied.set(false), 1500); + } +} diff --git a/projects/element-ng/markdown/extensions/code/si-markdown-code.extension.ts b/projects/element-ng/markdown/extensions/code/si-markdown-code.extension.ts new file mode 100644 index 0000000000..7e5745a248 --- /dev/null +++ b/projects/element-ng/markdown/extensions/code/si-markdown-code.extension.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { type SiMarkdownExtension } from '../../si-markdown.types'; +import { type MarkdownCodeOptions, SiMarkdownCodeComponent } from './si-markdown-code.component'; + +/** Extension to render code blocks. This is always installed automatically. */ +export const siMarkdownCode = (options: MarkdownCodeOptions): SiMarkdownExtension => { + return { + types: [{ type: 'code', component: SiMarkdownCodeComponent, options }] + }; +}; diff --git a/projects/element-ng/markdown/extensions/inline-html/index.ts b/projects/element-ng/markdown/extensions/inline-html/index.ts new file mode 100644 index 0000000000..770e21e8e9 --- /dev/null +++ b/projects/element-ng/markdown/extensions/inline-html/index.ts @@ -0,0 +1,5 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +export * from './si-markdown-inline-html.extension'; diff --git a/projects/element-ng/markdown/extensions/inline-html/si-markdown-inline-html.component.ts b/projects/element-ng/markdown/extensions/inline-html/si-markdown-inline-html.component.ts new file mode 100644 index 0000000000..b875ece197 --- /dev/null +++ b/projects/element-ng/markdown/extensions/inline-html/si-markdown-inline-html.component.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { Component, computed, inject, input, SecurityContext } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import { type Parent, type Literal, type Node } from 'mdast'; + +import { SiMarkdownExtensionComponent } from '../../si-markdown.types'; + +@Component({ + selector: 'si-markdown-inline-html', + template: '', + host: { + '[innerHTML]': 'sanitized()' + } +}) +export class SiMarkdownInlineHtmlComponent implements SiMarkdownExtensionComponent { + private readonly sanitizer = inject(DomSanitizer); + readonly node = input.required(); + readonly parent = input.required(); + readonly options = input(); + private readonly html = computed(() => (this.node() as Literal).value); + + protected readonly sanitized = computed(() => { + const html = this.html(); + // because those are very common + if (html === '
' || html === '
') { + return this.sanitizer.bypassSecurityTrustHtml(html); + } + return this.sanitizer.sanitize(SecurityContext.HTML, html); + }); +} diff --git a/projects/element-ng/markdown/extensions/inline-html/si-markdown-inline-html.extension.ts b/projects/element-ng/markdown/extensions/inline-html/si-markdown-inline-html.extension.ts new file mode 100644 index 0000000000..4ac9ad4e51 --- /dev/null +++ b/projects/element-ng/markdown/extensions/inline-html/si-markdown-inline-html.extension.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { type SiMarkdownExtension } from '../../si-markdown.types'; +import { SiMarkdownInlineHtmlComponent } from './si-markdown-inline-html.component'; + +/** Extension to render sanitized inline HTML. This is always installed automatically. */ +export const siMarkdownInlineHtml = (): SiMarkdownExtension => { + return { + types: [{ type: 'html', component: SiMarkdownInlineHtmlComponent }] + }; +}; diff --git a/projects/element-ng/markdown/extensions/katex/index.ts b/projects/element-ng/markdown/extensions/katex/index.ts new file mode 100644 index 0000000000..7acff1be21 --- /dev/null +++ b/projects/element-ng/markdown/extensions/katex/index.ts @@ -0,0 +1,5 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +export * from './si-markdown-katex.extension'; diff --git a/projects/element-ng/markdown/extensions/katex/si-markdown-katex.component.scss b/projects/element-ng/markdown/extensions/katex/si-markdown-katex.component.scss new file mode 100644 index 0000000000..87059f83d0 --- /dev/null +++ b/projects/element-ng/markdown/extensions/katex/si-markdown-katex.component.scss @@ -0,0 +1,3 @@ +@use 'katex/src/styles/katex' with ( + $font-folder: 'katex/dist/fonts' +); diff --git a/projects/element-ng/markdown/extensions/katex/si-markdown-katex.component.spec.ts b/projects/element-ng/markdown/extensions/katex/si-markdown-katex.component.spec.ts new file mode 100644 index 0000000000..408394fef3 --- /dev/null +++ b/projects/element-ng/markdown/extensions/katex/si-markdown-katex.component.spec.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SiMarkdownOptions } from '../../si-markdown-options'; +import { SiMarkdownComponent } from '../../si-markdown.component'; +import { siMarkdownMathKaTeX } from './si-markdown-katex.extension'; + +describe('SiMarkdownKatexComponent', () => { + let fixture: ComponentFixture; + let element: HTMLElement; + + beforeEach(() => { + fixture = TestBed.createComponent(SiMarkdownComponent); + element = fixture.nativeElement; + }); + + it('renders inline and display math when the KaTeX extension is installed', async () => { + fixture.componentRef.setInput( + 'options', + new SiMarkdownOptions().installExtension(siMarkdownMathKaTeX()) + ); + fixture.componentRef.setInput('markdown', 'Inline math: $x^2$.\n\n$$\ny^2\n$$'); + fixture.detectChanges(); + await new Promise(resolve => setTimeout(resolve)); + await fixture.whenStable(); + + const inlineMath = element.querySelector('si-markdown-katex:not(.d-block)'); + const displayMath = element.querySelector('si-markdown-katex.d-block'); + + expect(inlineMath).not.toBeNull(); + expect(inlineMath!).not.toHaveClass('d-block'); + expect(inlineMath?.querySelector('.katex-html')).toHaveTextContent('x2'); + expect(displayMath).not.toBeNull(); + expect(displayMath!).toHaveClass('d-block'); + expect(displayMath?.querySelector('.katex-html')).toHaveTextContent('y2'); + }); +}); diff --git a/projects/element-ng/markdown/extensions/katex/si-markdown-katex.component.ts b/projects/element-ng/markdown/extensions/katex/si-markdown-katex.component.ts new file mode 100644 index 0000000000..0acf9bbbfc --- /dev/null +++ b/projects/element-ng/markdown/extensions/katex/si-markdown-katex.component.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { + Component, + computed, + inject, + input, + SecurityContext, + ViewEncapsulation +} from '@angular/core'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { type KatexOptions, renderToString } from 'katex'; +import { Literal, type Node, type Parent } from 'mdast'; + +import { SiMarkdownExtensionComponent } from '../../si-markdown.types'; + +@Component({ + selector: 'si-markdown-katex', + template: '', + styleUrl: 'si-markdown-katex.component.scss', + // this is necessary for the styles to work correctly + // eslint-disable-next-line @angular-eslint/use-component-view-encapsulation + encapsulation: ViewEncapsulation.None, + host: { + class: 'katex-container', + '[class.d-block]': 'displayMode()', + '[class.text-danger]': 'html().error', + '[title]': 'html().error', + '[innerHTML]': 'html().html' + } +}) +export class SiMarkdownKatexComponent implements SiMarkdownExtensionComponent { + private readonly sanitizer = inject(DomSanitizer); + + readonly node = input.required(); + readonly parent = input.required(); + readonly options = input(); + + protected readonly displayMode = computed(() => this.node().type === 'math'); + private readonly expr = computed(() => (this.node() as Literal).value); + protected readonly html = computed(() => this.render()); + + private render(): { html: SafeHtml; error: string } { + const expr = this.expr(); + try { + const options = this.options() ?? {}; + const html = renderToString(expr, { ...options, displayMode: this.displayMode() }); + return { + html: this.sanitizer.bypassSecurityTrustHtml(html), + error: '' + }; + } catch (error: any) { + return { + html: this.sanitizer.sanitize(SecurityContext.HTML, expr) as SafeHtml, + error: error.toString() + }; + } + } +} diff --git a/projects/element-ng/markdown/extensions/katex/si-markdown-katex.extension.ts b/projects/element-ng/markdown/extensions/katex/si-markdown-katex.extension.ts new file mode 100644 index 0000000000..30477a189d --- /dev/null +++ b/projects/element-ng/markdown/extensions/katex/si-markdown-katex.extension.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { type KatexOptions } from 'katex'; +import remarkMath, { type Options } from 'remark-math'; + +import { SiMarkdownExtension } from '../../si-markdown.types'; +import { SiMarkdownKatexComponent } from './si-markdown-katex.component'; + +/** + * An extension to render LaTeX math expressions using KaTeX. Uses `remark-math` to parse + * the expressions inside the markdown document. + * @param parseOptions - options passed to `remark-math` + * @param katexOptions - options passed to `katex` + * @returns extension for `SiMarkdownOption.installExtension()` + */ +export const siMarkdownMathKaTeX = ( + parseOptions?: Options, + katexOptions?: KatexOptions +): SiMarkdownExtension => { + katexOptions ??= { + output: 'html' + }; + + return { + plugins: [{ plugin: remarkMath, options: parseOptions }], + types: [ + { type: 'math', component: SiMarkdownKatexComponent, options: katexOptions }, + { type: 'inlineMath', component: SiMarkdownKatexComponent, options: katexOptions } + ] + }; +}; diff --git a/projects/element-ng/markdown/extensions/mermaid/index.ts b/projects/element-ng/markdown/extensions/mermaid/index.ts new file mode 100644 index 0000000000..aa5ca0b3fb --- /dev/null +++ b/projects/element-ng/markdown/extensions/mermaid/index.ts @@ -0,0 +1,5 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +export * from './si-markdown-mermaid.extension'; diff --git a/projects/element-ng/markdown/extensions/mermaid/si-markdown-mermaid.component.spec.ts b/projects/element-ng/markdown/extensions/mermaid/si-markdown-mermaid.component.spec.ts new file mode 100644 index 0000000000..9ec178fbff --- /dev/null +++ b/projects/element-ng/markdown/extensions/mermaid/si-markdown-mermaid.component.spec.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SiMarkdownOptions } from '../../si-markdown-options'; +import { SiMarkdownComponent } from '../../si-markdown.component'; +import { siMarkdownMermaid } from './si-markdown-mermaid.extension'; + +describe('SiMarkdownMermaidComponent', () => { + let fixture: ComponentFixture; + let element: HTMLElement; + + beforeEach(() => { + fixture = TestBed.createComponent(SiMarkdownComponent); + element = fixture.nativeElement; + }); + + it('renders a Mermaid fenced code block when the extension is installed', async () => { + fixture.componentRef.setInput( + 'options', + new SiMarkdownOptions().installExtension(siMarkdownMermaid()) + ); + fixture.componentRef.setInput('markdown', '```mermaid\ngraph TD\n Start --> End\n```'); + fixture.detectChanges(); + + await vi.waitFor(() => { + expect(element.querySelector('si-markdown-mermaid svg')).not.toBeNull(); + }); + + const mermaid = element.querySelector('si-markdown-mermaid'); + + expect(mermaid).not.toBeNull(); + expect(mermaid?.querySelector('svg')).toHaveTextContent(/Start.*End/); + expect(element.querySelector('si-markdown-code')).toBeNull(); + }); +}); diff --git a/projects/element-ng/markdown/extensions/mermaid/si-markdown-mermaid.component.ts b/projects/element-ng/markdown/extensions/mermaid/si-markdown-mermaid.component.ts new file mode 100644 index 0000000000..ada0559cc1 --- /dev/null +++ b/projects/element-ng/markdown/extensions/mermaid/si-markdown-mermaid.component.ts @@ -0,0 +1,51 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { Component, computed, inject, input } from '@angular/core'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { Literal, type Node, type Parent } from 'mdast'; +import mermaid, { MermaidConfig } from 'mermaid'; + +import { SiMarkdownExtensionComponent } from '../../si-markdown.types'; +import { computedAsync } from '../../utils/signal-utils'; + +@Component({ + selector: 'si-markdown-mermaid', + template: '
' +}) +export class SiMarkdownMermaidComponent implements SiMarkdownExtensionComponent { + private static idCounter = 1; + protected id = `__si-markdown-mermaid-${SiMarkdownMermaidComponent.idCounter++}`; + + private readonly sanitizer = inject(DomSanitizer); + readonly node = input.required(); + readonly parent = input.required(); + readonly options = input(); + + protected readonly code = computed(() => (this.node() as Literal).value); + + protected readonly svg = computedAsync({ + params: () => ({ options: this.options(), code: this.code() }), + computation: params => this.render(params.code, params.options), + initial: undefined + }); + + constructor() { + mermaid.initialize({ startOnLoad: false, suppressErrorRendering: true }); + } + + private async render(code: string, options: MermaidConfig = {}): Promise { + if (!code) { + return ''; + } + try { + mermaid.initialize({ ...options, startOnLoad: false, suppressErrorRendering: true }); + await new Promise(resolve => setTimeout(resolve)); // this is necessary for mermaid to init + const ret = await mermaid.render(this.id, code); + return this.sanitizer.bypassSecurityTrustHtml(ret.svg); + } catch (err) { + return ''; + } + } +} diff --git a/projects/element-ng/markdown/extensions/mermaid/si-markdown-mermaid.extension.ts b/projects/element-ng/markdown/extensions/mermaid/si-markdown-mermaid.extension.ts new file mode 100644 index 0000000000..8ff9d0507a --- /dev/null +++ b/projects/element-ng/markdown/extensions/mermaid/si-markdown-mermaid.extension.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ + +import { MermaidConfig } from 'mermaid'; + +import { SiMarkdownExtension } from '../../si-markdown.types'; +import { SiMarkdownMermaidComponent } from './si-markdown-mermaid.component'; + +export const siMarkdownMermaid = (options?: MermaidConfig): SiMarkdownExtension => { + return { + codeTypes: [{ type: 'mermaid', component: SiMarkdownMermaidComponent, options }] + }; +}; diff --git a/projects/element-ng/markdown/extensions/source-citations/index.ts b/projects/element-ng/markdown/extensions/source-citations/index.ts new file mode 100644 index 0000000000..9ad7b4f734 --- /dev/null +++ b/projects/element-ng/markdown/extensions/source-citations/index.ts @@ -0,0 +1,5 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +export * from './si-markdown-source-citation.extension'; \ No newline at end of file diff --git a/projects/element-ng/markdown/extensions/source-citations/si-markdown-source-citation.component.html b/projects/element-ng/markdown/extensions/source-citations/si-markdown-source-citation.component.html new file mode 100644 index 0000000000..2d3a0cdd04 --- /dev/null +++ b/projects/element-ng/markdown/extensions/source-citations/si-markdown-source-citation.component.html @@ -0,0 +1,31 @@ + + + + Sources + +
    + @for (citation of citations(); track citation.reference) { +
  • + +
  • + } +
+
+
\ No newline at end of file diff --git a/projects/element-ng/markdown/extensions/source-citations/si-markdown-source-citation.component.scss b/projects/element-ng/markdown/extensions/source-citations/si-markdown-source-citation.component.scss new file mode 100644 index 0000000000..9ef016e058 --- /dev/null +++ b/projects/element-ng/markdown/extensions/source-citations/si-markdown-source-citation.component.scss @@ -0,0 +1,50 @@ +@use 'sass:map'; +@use '@siemens/element-theme/src/styles/variables'; + +:host { + display: inline-block; + margin-inline: map.get(variables.$spacers, 3); + vertical-align: text-bottom; +} + +.source-citation-trigger { + display: inline-flex; + align-items: center; + block-size: map.get(variables.$spacers, 6); + padding: 0; + border: 0; + border-radius: map.get(variables.$spacers, 6); + background: transparent; + color: inherit; + text-align: start; + + &:focus-visible { + @include variables.make-outline-focus(); + } +} + +:host ::ng-deep si-summary-chip .chip { + box-sizing: border-box; + block-size: map.get(variables.$spacers, 6); + min-block-size: map.get(variables.$spacers, 6); + padding-block: 0; + padding-inline: map.get(variables.$spacers, 3); +} + +:host ::ng-deep si-summary-chip .si-body { + font-size: variables.$si-font-size-caption; + line-height: variables.$si-line-height-caption; +} + +:host ::ng-deep .source-citation-popover { + display: flex; + flex-direction: column; + min-inline-size: 14.375rem; + max-inline-size: 25rem; + max-block-size: 25rem; +} + +:host ::ng-deep .source-citation-popover .popover-body { + min-block-size: 0; + overflow-y: auto; +} \ No newline at end of file diff --git a/projects/element-ng/markdown/extensions/source-citations/si-markdown-source-citation.component.spec.ts b/projects/element-ng/markdown/extensions/source-citations/si-markdown-source-citation.component.spec.ts new file mode 100644 index 0000000000..48e1781c3b --- /dev/null +++ b/projects/element-ng/markdown/extensions/source-citations/si-markdown-source-citation.component.spec.ts @@ -0,0 +1,90 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SiMarkdownOptions } from '../../si-markdown-options'; +import { SiMarkdownComponent } from '../../si-markdown.component'; +import { SiMarkdownCitation, siMarkdownSourceCitations } from './si-markdown-source-citation.extension'; + +describe('SiMarkdownSourceCitationComponent', () => { + let fixture: ComponentFixture; + let element: HTMLElement; + let citations: SiMarkdownCitation[]; + let onSourceOpen: ReturnType void>>; + + beforeEach(() => { + citations = [ + { + reference: '1', + name: 'Data Analysis Guide', + url: 'https://example.com/data-analysis', + quote: 'Validate the data structure before analysis.' + }, + { + reference: '2', + name: 'Large Dataset Performance Guide', + url: 'https://example.com/large-datasets', + description: 'Use incremental processing for production datasets.' + } + ]; + onSourceOpen = vi.fn<(citation: SiMarkdownCitation) => void>(); + fixture = TestBed.createComponent(SiMarkdownComponent); + element = fixture.nativeElement; + fixture.componentRef.setInput( + 'options', + new SiMarkdownOptions().installExtension( + siMarkdownSourceCitations({ citations, onSourceOpen }) + ) + ); + }); + + it('groups resolved citations into one trailing source chip and opens its sources', async () => { + fixture.componentRef.setInput( + 'markdown', + 'Validate the data structure before analysis.[1] Then process it incrementally.[2][1]' + ); + fixture.detectChanges(); + await new Promise(resolve => setTimeout(resolve)); + await fixture.whenStable(); + + const sourceCitation = element.querySelector('si-markdown-source-citation'); + const trigger = sourceCitation?.querySelector('button'); + + expect(element.querySelectorAll('si-markdown-source-citation')).toHaveLength(1); + expect(element.querySelector('p')).not.toHaveTextContent('[1]'); + expect(element.querySelector('p')).not.toHaveTextContent('[2]'); + expect(trigger).toHaveAccessibleName('View sources: Data Analysis Guide and 1 additional source'); + expect(trigger).toHaveTextContent('Data Analysis Guide +1'); + expect(trigger?.querySelector('.chip')).not.toHaveAttribute('role'); + + vi.useFakeTimers({ shouldAdvanceTime: true }); + trigger?.click(); + await fixture.whenStable(); + vi.advanceTimersByTime(10); + await fixture.whenStable(); + + const sourceItems = document.querySelectorAll('.popover .list-item'); + expect(sourceItems).toHaveLength(2); + expect(sourceItems[0]).toHaveTextContent('Data Analysis Guide'); + expect(sourceItems[0]).toHaveTextContent('Validate the data structure before analysis.'); + expect(sourceItems[1]).toHaveTextContent('Large Dataset Performance Guide'); + expect(sourceItems[1]).toHaveTextContent('Use incremental processing for production datasets.'); + + (sourceItems[1] as HTMLButtonElement).click(); + expect(onSourceOpen).toHaveBeenCalledWith(citations[1]); + vi.useRealTimers(); + }); + + it('leaves unresolved markers and citation-shaped markdown links unchanged', async () => { + fixture.componentRef.setInput('markdown', 'Unknown source [3]. [1](https://example.com)'); + fixture.detectChanges(); + await new Promise(resolve => setTimeout(resolve)); + await fixture.whenStable(); + + expect(element.querySelectorAll('si-markdown-source-citation')).toHaveLength(0); + expect(element.querySelector('p')).toHaveTextContent('Unknown source [3]. 1'); + expect(element.querySelector('a')).toHaveAttribute('href', 'https://example.com'); + }); +}); \ No newline at end of file diff --git a/projects/element-ng/markdown/extensions/source-citations/si-markdown-source-citation.component.ts b/projects/element-ng/markdown/extensions/source-citations/si-markdown-source-citation.component.ts new file mode 100644 index 0000000000..2925d3e89b --- /dev/null +++ b/projects/element-ng/markdown/extensions/source-citations/si-markdown-source-citation.component.ts @@ -0,0 +1,55 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { Component, computed, input } from '@angular/core'; +import { SiPopoverBodyDirective, SiPopoverDirective, SiPopoverTitleDirective } from '@siemens/element-ng/popover'; +import { SiSummaryChipComponent } from '@siemens/element-ng/summary-chip'; +import { type Node, type Parent } from 'mdast'; + +import { SiMarkdownExtensionComponent } from '../../si-markdown.types'; +import type { + SiMarkdownCitation, + SiMarkdownSourceCitationNode, + SiMarkdownSourceCitationsOptions +} from './si-markdown-source-citation.extension'; + +@Component({ + selector: 'si-markdown-source-citation', + imports: [ + SiPopoverBodyDirective, + SiPopoverDirective, + SiPopoverTitleDirective, + SiSummaryChipComponent + ], + templateUrl: './si-markdown-source-citation.component.html', + styleUrl: './si-markdown-source-citation.component.scss' +}) +export class SiMarkdownSourceCitationComponent implements SiMarkdownExtensionComponent { + readonly node = input.required(); + readonly parent = input.required(); + readonly options = input(); + + protected readonly citations = computed( + () => (this.node() as SiMarkdownSourceCitationNode).citations + ); + protected readonly label = computed(() => { + const citations = this.citations(); + const [firstCitation] = citations; + return citations.length > 1 ? `${firstCitation.name} +${citations.length - 1}` : firstCitation.name; + }); + protected readonly triggerLabel = computed(() => { + const citations = this.citations(); + const [firstCitation] = citations; + const additionalSources = citations.length - 1; + return additionalSources > 0 + ? `View sources: ${firstCitation.name} and ${additionalSources} additional source${ + additionalSources === 1 ? '' : 's' + }` + : `View source: ${firstCitation.name}`; + }); + + protected openSource(citation: SiMarkdownCitation): void { + this.options()?.onSourceOpen?.(citation); + } +} \ No newline at end of file diff --git a/projects/element-ng/markdown/extensions/source-citations/si-markdown-source-citation.extension.ts b/projects/element-ng/markdown/extensions/source-citations/si-markdown-source-citation.extension.ts new file mode 100644 index 0000000000..221bc5ae51 --- /dev/null +++ b/projects/element-ng/markdown/extensions/source-citations/si-markdown-source-citation.extension.ts @@ -0,0 +1,169 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { type Node, type Parent, type Root } from 'mdast'; + +import { SiMarkdownExtension } from '../../si-markdown.types'; +import { SiMarkdownSourceCitationComponent } from './si-markdown-source-citation.component'; + +/** A source that can be referenced in AI-generated markdown. */ +export interface SiMarkdownCitation { + /** Numeric reference emitted in markdown, for example `1` for `[1]`. */ + reference: string; + /** Source name displayed in the source chip and popover. */ + name: string; + /** Source URL. */ + url: string; + /** Optional source description. */ + description?: string; + /** Optional source quote. Takes precedence over {@link description}. */ + quote?: string; +} + +/** Configuration for {@link siMarkdownSourceCitations}. */ +export interface SiMarkdownSourceCitationsOptions { + /** Sources available to the markdown message. */ + citations: readonly SiMarkdownCitation[]; + /** Called when a source is selected in the popover. */ + onSourceOpen?: (citation: SiMarkdownCitation) => void; +} + +/** Markdown AST node rendered as a source chip. */ +export interface SiMarkdownSourceCitationNode extends Parent { + type: 'sourceCitation'; + citations: SiMarkdownCitation[]; +} + +type MarkdownParent = Node & { children?: Node[] }; +type MarkdownText = Node & { type: 'text'; value: string }; + +const citationMarker = /\[(\d+)]/g; + +/** + * Adds support for LLM-style numeric source citations such as `[1]`. + * + * Known citations are collected per paragraph and rendered as one summary chip at the end of + * that paragraph. Unknown citation markers stay unchanged in the rendered markdown. + * + * @param options - Sources available to the rendered markdown and an optional selection handler. + * @returns extension for {@link SiMarkdownOptions.installExtension}. + */ +export const siMarkdownSourceCitations = ( + options: SiMarkdownSourceCitationsOptions +): SiMarkdownExtension => { + return { + plugins: [{ plugin: sourceCitationPlugin, options }], + types: [ + { + type: 'sourceCitation', + component: SiMarkdownSourceCitationComponent, + options + } + ] + }; +}; + +const sourceCitationPlugin = (options: SiMarkdownSourceCitationsOptions) => { + const citationsByReference = new Map( + options.citations.map(citation => [citation.reference, citation]) + ); + + return (tree: Root) => transformTree(tree, citationsByReference); +}; + +const transformTree = (node: MarkdownParent, citationsByReference: Map): void => { + if (node.type === 'paragraph') { + transformParagraph(node, citationsByReference); + } + + for (const child of node.children ?? []) { + transformTree(child as MarkdownParent, citationsByReference); + } +}; + +const transformParagraph = ( + paragraph: MarkdownParent, + citationsByReference: Map +): void => { + const citations: SiMarkdownCitation[] = []; + const references = new Set(); + + replaceCitationMarkers(paragraph, citationsByReference, citations, references); + + if (citations.length > 0) { + (paragraph.children ??= []).push({ + type: 'sourceCitation', + citations + } as SiMarkdownSourceCitationNode); + } +}; + +const replaceCitationMarkers = ( + parent: MarkdownParent, + citationsByReference: Map, + citations: SiMarkdownCitation[], + references: Set +): void => { + if (!parent.children) { + return; + } + + parent.children = parent.children.flatMap(child => { + if (child.type === 'text') { + return splitTextNode(child as MarkdownText, citationsByReference, citations, references); + } + + if (!isCitationExcluded(child)) { + replaceCitationMarkers(child as MarkdownParent, citationsByReference, citations, references); + } + + return child; + }); +}; + +const splitTextNode = ( + textNode: MarkdownText, + citationsByReference: Map, + citations: SiMarkdownCitation[], + references: Set +): Node[] => { + const nodes: MarkdownText[] = []; + let lastIndex = 0; + let match: RegExpExecArray | null; + + citationMarker.lastIndex = 0; + while ((match = citationMarker.exec(textNode.value))) { + const citation = citationsByReference.get(match[1]); + if (!citation) { + continue; + } + + if (match.index > lastIndex) { + nodes.push({ type: 'text', value: textNode.value.slice(lastIndex, match.index) } as MarkdownText); + } + + if (!references.has(citation.reference)) { + citations.push(citation); + references.add(citation.reference); + } + + lastIndex = match.index + match[0].length; + } + + if (lastIndex === 0) { + return [textNode]; + } + + if (lastIndex < textNode.value.length) { + nodes.push({ type: 'text', value: textNode.value.slice(lastIndex) } as MarkdownText); + } + + return nodes; +}; + +const isCitationExcluded = (node: Node): boolean => + node.type === 'inlineCode' || + node.type === 'link' || + node.type === 'linkReference' || + node.type === 'html'; \ No newline at end of file diff --git a/projects/element-ng/markdown/hightlighter/highlightjs/hljs-language-loader.ts b/projects/element-ng/markdown/hightlighter/highlightjs/hljs-language-loader.ts new file mode 100644 index 0000000000..ca378a6745 --- /dev/null +++ b/projects/element-ng/markdown/hightlighter/highlightjs/hljs-language-loader.ts @@ -0,0 +1,97 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ + +/** load to dynamically load Highlight.js languages, still only a subset */ +export const hljsLanguageLoader = async ( + lang: string +): Promise => { + switch (lang) { + case 'applescript': + return import('highlight.js/lib/languages/applescript'); + case 'basic': + return import('highlight.js/lib/languages/basic'); + case 'c': + return import('highlight.js/lib/languages/c'); + case 'cmake': + return import('highlight.js/lib/languages/cmake'); + case 'cpp': + return import('highlight.js/lib/languages/cpp'); + case 'csharp': + return import('highlight.js/lib/languages/csharp'); + case 'diff': + return import('highlight.js/lib/languages/diff'); + case 'dockerfile': + return import('highlight.js/lib/languages/dockerfile'); + case 'erb': + return import('highlight.js/lib/languages/erb'); + case 'go': + return import('highlight.js/lib/languages/go'); + case 'gradle': + return import('highlight.js/lib/languages/gradle'); + case 'graphql': + return import('highlight.js/lib/languages/graphql'); + case 'groovy': + return import('highlight.js/lib/languages/groovy'); + case 'ini': + return import('highlight.js/lib/languages/ini'); + case 'java': + return import('highlight.js/lib/languages/java'); + case 'julia': + return import('highlight.js/lib/languages/julia'); + case 'latex': + return import('highlight.js/lib/languages/latex'); + case 'llvm': + return import('highlight.js/lib/languages/llvm'); + case 'lua': + return import('highlight.js/lib/languages/lua'); + case 'makefile': + return import('highlight.js/lib/languages/makefile'); + case 'markdown': + return import('highlight.js/lib/languages/markdown'); + case 'mathematica': + return import('highlight.js/lib/languages/mathematica'); + case 'matlab': + return import('highlight.js/lib/languages/matlab'); + case 'objectivec': + return import('highlight.js/lib/languages/objectivec'); + case 'perl': + return import('highlight.js/lib/languages/perl'); + case 'pgsql': + return import('highlight.js/lib/languages/pgsql'); + case 'php': + return import('highlight.js/lib/languages/php'); + case 'powershell': + return import('highlight.js/lib/languages/powershell'); + case 'protobuf': + return import('highlight.js/lib/languages/protobuf'); + case 'ruby': + return import('highlight.js/lib/languages/ruby'); + case 'rust': + return import('highlight.js/lib/languages/rust'); + case 'scala': + return import('highlight.js/lib/languages/scala'); + case 'scheme': + return import('highlight.js/lib/languages/scheme'); + case 'scilab': + return import('highlight.js/lib/languages/scilab'); + case 'sql': + return import('highlight.js/lib/languages/sql'); + case 'swift': + return import('highlight.js/lib/languages/swift'); + case 'tcl': + return import('highlight.js/lib/languages/tcl'); + case 'vbnet': + return import('highlight.js/lib/languages/vbnet'); + case 'vbscript': + return import('highlight.js/lib/languages/vbscript'); + case 'vim': + return import('highlight.js/lib/languages/vim'); + case 'wasm': + return import('highlight.js/lib/languages/wasm'); + case 'yaml': + return import('highlight.js/lib/languages/yaml'); + } + return undefined; +}; diff --git a/projects/element-ng/markdown/hightlighter/highlightjs/index.ts b/projects/element-ng/markdown/hightlighter/highlightjs/index.ts new file mode 100644 index 0000000000..7153054bdf --- /dev/null +++ b/projects/element-ng/markdown/hightlighter/highlightjs/index.ts @@ -0,0 +1,6 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +export * from './si-markdown-highlightjs.types'; +export * from './si-markdown-highlightjs.highlighter'; diff --git a/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.component.html b/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.component.html new file mode 100644 index 0000000000..7f0bb72e65 --- /dev/null +++ b/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.component.html @@ -0,0 +1,6 @@ +@let html = highlighted(); +@if (html) { +
+} @else { +
{{ code() }} 
+} diff --git a/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.component.scss b/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.component.scss new file mode 100644 index 0000000000..560d2e091d --- /dev/null +++ b/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.component.scss @@ -0,0 +1,119 @@ +@use '@siemens/element-theme/src/styles/variables'; + +// stylelint-disable selector-class-pattern + +// Custom highlight.js theme using Element Design System tokens +// Automatically adapts to light and dark themes + +:host ::ng-deep { + // 1. Default and text + .hljs { + color: variables.$element-text-primary; + } + + // 2. Keywords + :is(.hljs-keyword, .hljs-literal) { + color: variables.$element-data-purple-2; + } + + // 3. Special keywords and operators + .hljs-operator { + color: variables.$element-data-plum-2; + } + + // 4. Variables + :is( + .hljs-variable, + .hljs-variable.language_, + .hljs-variable.constant_, + .hljs-params, + .hljs-property + ) { + color: variables.$element-data-6; + } + + // 5. Functions and methods + :is(.hljs-title, .hljs-title.function_, .hljs-title.function_.invoke__, .hljs-built_in) { + color: variables.$element-text-caution; + } + + // 6. Strings + :is(.hljs-string, .hljs-regexp, .hljs-char.escape_, .hljs-subst, .hljs-doctag) { + color: variables.$element-text-warning; + } + + // 7. Numbers + .hljs-number { + color: variables.$element-text-success; + } + + // 8. Constants and enums + :is(.hljs-symbol, .hljs-bullet) { + color: variables.$element-data-13; + } + + // 9. Comments + :is(.hljs-comment, .hljs-quote) { + color: variables.$element-data-green-3; + } + + // 10. Brackets/punctuation + .hljs-punctuation { + color: variables.$element-data-17; + } + + // 11. Tags (HTML/XML) + :is( + .hljs-tag, + .hljs-name, + .hljs-attr, + .hljs-attribute, + .hljs-selector-tag, + .hljs-selector-id, + .hljs-selector-class, + .hljs-selector-attr, + .hljs-selector-pseudo, + .hljs-meta, + .hljs-meta.keyword_, + .hljs-template-tag, + .hljs-template-variable + ) { + color: variables.$element-data-5; + } + + // 12. Types declaration and references + :is(.hljs-type, .hljs-title.class_, .hljs-title.class_.inherited__, .hljs-section) { + color: variables.$element-data-4; + } + + // 13. Invalid + .hljs-deletion { + color: variables.$element-data-10; + } + + // Additions in diffs (use comment green) + .hljs-addition { + color: variables.$element-data-green-3; + } + + // Emphasis + .hljs-emphasis { + font-style: italic; + } + + // Strong emphasis + .hljs-strong { + font-weight: variables.$si-font-weight-semibold; + } + + // Code blocks and formulas + .hljs-code, + .hljs-formula { + color: variables.$element-text-primary; + } + + // Links + .hljs-link { + color: variables.$element-text-warning; + } +} diff --git a/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.component.spec.ts b/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.component.spec.ts new file mode 100644 index 0000000000..dee83585ed --- /dev/null +++ b/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.component.spec.ts @@ -0,0 +1,84 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SiMarkdownOptions } from '../../si-markdown-options'; +import { SiMarkdownComponent } from '../../si-markdown.component'; +import { siMarkdownHighlightJs } from './si-markdown-highlightjs.highlighter'; + +describe('SiMarkdownHightlightJsComponent', () => { + let fixture: ComponentFixture; + let element: HTMLElement; + + beforeEach(() => { + fixture = TestBed.createComponent(SiMarkdownComponent); + element = fixture.nativeElement; + }); + + it('highlights a fenced code block when configured as the code highlighter', async () => { + fixture.componentRef.setInput( + 'options', + new SiMarkdownOptions().setCodeHighlighter(siMarkdownHighlightJs()) + ); + fixture.componentRef.setInput('markdown', '```typescript\nconst answer = 42;\n```'); + fixture.detectChanges(); + + await vi.waitFor(() => { + expect(element.querySelector('si-markdown-highlightjs .hljs-keyword')).not.toBeNull(); + }); + + const codeBlock = element.querySelector('si-markdown-code'); + const highlighter = codeBlock?.querySelector('si-markdown-highlightjs'); + + expect(codeBlock).not.toBeNull(); + expect(codeBlock?.querySelector('.code-language')).toHaveTextContent('TypeScript'); + expect(highlighter).not.toBeNull(); + expect(highlighter?.querySelector('pre code')).toHaveTextContent('const answer = 42;'); + expect(highlighter?.querySelector('.hljs-keyword')).toHaveTextContent('const'); + }); + + it('loads a Python language definition before highlighting a code block', async () => { + fixture.componentRef.setInput( + 'options', + new SiMarkdownOptions().setCodeHighlighter(siMarkdownHighlightJs()) + ); + fixture.componentRef.setInput('markdown', '```python\ndef greet():\n return "Hello"\n```'); + fixture.detectChanges(); + + await vi.waitFor(() => { + expect(element.querySelector('si-markdown-highlightjs .hljs-keyword')).not.toBeNull(); + }); + + const codeBlock = element.querySelector('si-markdown-code'); + const highlighter = codeBlock?.querySelector('si-markdown-highlightjs'); + + expect(codeBlock?.querySelector('.code-language')).toHaveTextContent('Python'); + expect(highlighter?.querySelector('pre code')).toHaveTextContent('def greet(): return "Hello"'); + expect(highlighter?.querySelector('.hljs-keyword')).toHaveTextContent('def'); + }); + + it('detects the language of an unlabeled fenced code block when enabled', async () => { + fixture.componentRef.setInput( + 'options', + new SiMarkdownOptions().setCodeHighlighter( + siMarkdownHighlightJs({ autoDetectLanguage: true }) + ) + ); + fixture.componentRef.setInput('markdown', '```\ninterface User { name: string; }\n```'); + fixture.detectChanges(); + await new Promise(resolve => setTimeout(resolve)); + + await vi.waitFor(() => { + expect(element.querySelector('.code-language')).toHaveTextContent('TypeScript'); + }); + + const highlighter = element.querySelector('si-markdown-highlightjs'); + + expect(highlighter?.querySelector('pre code')).toHaveTextContent( + 'interface User { name: string; }' + ); + expect(highlighter?.querySelector('.hljs-keyword')).toHaveTextContent('interface'); + }); +}); diff --git a/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.component.ts b/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.component.ts new file mode 100644 index 0000000000..e44655ca1e --- /dev/null +++ b/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.component.ts @@ -0,0 +1,113 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { Component, effect, inject, input } from '@angular/core'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { type Language } from 'highlight.js'; +import hljs from 'highlight.js/lib/core'; +import langBash from 'highlight.js/lib/languages/bash'; +import langCss from 'highlight.js/lib/languages/css'; +import langJs from 'highlight.js/lib/languages/javascript'; +import langJson from 'highlight.js/lib/languages/json'; +import langPython from 'highlight.js/lib/languages/python'; +import langScss from 'highlight.js/lib/languages/scss'; +import langTs from 'highlight.js/lib/languages/typescript'; +import langXml from 'highlight.js/lib/languages/xml'; + +import { SiMarkdownHighlighterComponent } from '../../si-markdown.types'; +import { computedAsync } from '../../utils/signal-utils'; +import { hljsLanguageLoader } from './hljs-language-loader'; +import { SiMarkdownHighlightJsOptions } from './si-markdown-highlightjs.types'; + +// some common languages are built-in for initial loading speed +hljs.registerLanguage('javascript', langJs); +hljs.registerLanguage('typescript', langTs); +hljs.registerLanguage('json', langJson); +hljs.registerLanguage('css', langCss); +hljs.registerLanguage('scss', langScss); +hljs.registerLanguage('bash', langBash); +hljs.registerLanguage('python', langPython); +hljs.registerLanguage('xml', langXml); + +const getHljsLang = async (lang: string): Promise => { + const loadedLang = hljs.getLanguage(lang); + if (loadedLang) { + return loadedLang; + } + const importLang = await hljsLanguageLoader(lang); + if (importLang) { + hljs.registerLanguage(lang, importLang.default); + return hljs.getLanguage(lang); + } + return undefined; +}; + +@Component({ + selector: 'si-markdown-highlightjs', + templateUrl: './si-markdown-highlightjs.component.html', + styleUrl: './si-markdown-highlightjs.component.scss', + host: { + class: 'd-block' + } +}) +export class SiMarkdownHightlightJsComponent implements SiMarkdownHighlighterComponent { + private readonly sanitizer = inject(DomSanitizer); + readonly code = input.required(); + readonly language = input.required(); + readonly updateLanguage = input.required<(lang?: string) => void>(); + readonly options = input(); + + constructor() { + effect(() => { + const opts = this.options(); + for (const l of opts?.languages ?? []) { + if (!hljs.getLanguage(l.name)) { + hljs.registerLanguage(l.name, l.lang); + } + } + }); + } + + protected readonly highlighted = computedAsync({ + params: () => ({ code: this.code(), language: this.language(), options: this.options() }), + computation: params => this.highlight(params.code, params.language, params.options), + initial: undefined + }); + + private async highlight( + code: string, + language: string, + options: SiMarkdownHighlightJsOptions = {} + ): Promise { + let highlighted = ''; + if (options?.autoDetectLanguage) { + try { + const res = hljs.highlightAuto(code, language ? [language] : undefined); + highlighted = res.value; + if (res.language) { + const lang = hljs.getLanguage(res.language); + this.updateLanguage()(lang?.name); + } else { + this.updateLanguage()(language); + } + } catch { + // ignored + } + } else { + await new Promise(resolve => setTimeout(resolve)); + const hljsLang = await getHljsLang(language); + if (hljsLang) { + this.updateLanguage()(hljsLang.name); + try { + highlighted = hljs.highlight(code, { ...options, language }).value; + } catch { + // ignored + } + } else { + this.updateLanguage()(language); + } + } + return highlighted ? this.sanitizer.bypassSecurityTrustHtml(highlighted) : ''; + } +} diff --git a/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.highlighter.ts b/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.highlighter.ts new file mode 100644 index 0000000000..78c702cbc8 --- /dev/null +++ b/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.highlighter.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ + +import { SiMarkdownHighlighter } from '../../si-markdown.types'; +import { SiMarkdownHightlightJsComponent } from './si-markdown-highlightjs.component'; +import { SiMarkdownHighlightJsOptions } from './si-markdown-highlightjs.types'; + +/** + * Code highlighter using Highlight.js + * @param options - Options passed to highlight.js + * @returns highlighter configuration for `SiMarkdownOptions.setCodeHighlighter()` + */ +export const siMarkdownHighlightJs = ( + options?: SiMarkdownHighlightJsOptions +): SiMarkdownHighlighter => { + return { + component: SiMarkdownHightlightJsComponent, + options + }; +}; diff --git a/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.types.ts b/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.types.ts new file mode 100644 index 0000000000..b2e8283400 --- /dev/null +++ b/projects/element-ng/markdown/hightlighter/highlightjs/si-markdown-highlightjs.types.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { type HighlightOptions, type LanguageFn } from 'highlight.js'; + +/** Configuration for the Highlight.js highlighter */ +export interface SiMarkdownHighlightJsOptions { + /** Should language be auto-detected? This only works with already loaded languages */ + autoDetectLanguage?: boolean; + /** + * List of languages to eagerly load + * @example + * ``` + * [ + * { name: 'perl', lang: import('highlight.js/lib/languages/perl') } + * ] + * ``` + */ + languages?: { + name: string; + lang: LanguageFn; + }[]; + /** options passed to highlight.js */ + highlightJs?: Partial; +} diff --git a/projects/element-ng/markdown/index.ts b/projects/element-ng/markdown/index.ts new file mode 100644 index 0000000000..ec77231f8f --- /dev/null +++ b/projects/element-ng/markdown/index.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +export * from './si-markdown.types'; +export * from './si-markdown.component'; +export * from './si-markdown-fragment.component'; +export * from './extensions/inline-html/si-markdown-inline-html.component'; +export * from './si-markdown-template.directive'; +export * from './si-markdown-options'; diff --git a/projects/element-ng/markdown/ng-package.json b/projects/element-ng/markdown/ng-package.json new file mode 100644 index 0000000000..bb6786ef97 --- /dev/null +++ b/projects/element-ng/markdown/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} diff --git a/projects/element-ng/markdown/si-markdown-fragment.component.html b/projects/element-ng/markdown/si-markdown-fragment.component.html new file mode 100644 index 0000000000..8e40b847cf --- /dev/null +++ b/projects/element-ng/markdown/si-markdown-fragment.component.html @@ -0,0 +1,18 @@ + + +@let templates = control.templates(); +@for (child of children(); track $index) { + +} + + + @if (control.debug()) { +
+ Node type not implemented: {{ child.type }} +
{{ child | json }}
+
+ } +
diff --git a/projects/element-ng/markdown/si-markdown-fragment.component.ts b/projects/element-ng/markdown/si-markdown-fragment.component.ts new file mode 100644 index 0000000000..ad19b120d8 --- /dev/null +++ b/projects/element-ng/markdown/si-markdown-fragment.component.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { JsonPipe, NgTemplateOutlet } from '@angular/common'; +import { Component, computed, inject, input } from '@angular/core'; +import { type Node, type Parent } from 'mdast'; + +import { SI_MARKDOWN_CONTROL } from './si-markdown-token'; + +/** + * Renders a fragment of markdown based on the children of a single node in the AST. + */ +@Component({ + selector: 'si-markdown-fragment, [siMarkdownFragment]', + imports: [NgTemplateOutlet, JsonPipe], + templateUrl: './si-markdown-fragment.component.html', + host: { + class: 'markdown-content' + } +}) +export class SiMarkdownFragmentComponent { + protected readonly control = inject(SI_MARKDOWN_CONTROL); + /** The node for which to render all children */ + readonly node = input.required({ alias: 'siMarkdownFragment' }); + /** all the children of the node */ + readonly children = computed(() => (this.node() as Parent).children ?? []); +} diff --git a/projects/element-ng/markdown/si-markdown-options.ts b/projects/element-ng/markdown/si-markdown-options.ts new file mode 100644 index 0000000000..fdf00a8669 --- /dev/null +++ b/projects/element-ng/markdown/si-markdown-options.ts @@ -0,0 +1,126 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { type Root } from 'mdast'; +import remarkGfm from 'remark-gfm'; +import remarkParse from 'remark-parse'; +import { Processor, unified, type Plugin } from 'unified'; + +import { siMarkdownCode } from './extensions/code'; +import { siMarkdownInlineHtml } from './extensions/inline-html'; +import { + PluginWithOptions, + SiMarkdownExtension, + SiMarkdownHighlighter, + TypeHandler, + UnifiedPlugin +} from './si-markdown.types'; + +/** + * Options for the markdown renderer. This holds all configuration and allows installing + * extensions. A number of extensions are already ready to use and shipped with Element. + * + * @example + * ```ts + * import { siMarkdownMathKaTeX } from '@siemens/element-ng/markdown/extensions/math'; + * import remarkGemoji from 'remark-gemoji'; + * + * protected markdownOptions = new SiMarkdownOptions() + * .installExtension(siMarkdownMathKaTeX()) + * .installUnifiedPlugin(remarkGemoji); + * ``` + * + * ```html + * + * ``` + */ +export class SiMarkdownOptions { + private readonly plugins: PluginWithOptions[] = []; + private types: TypeHandler[] = []; + private codeTypes = new Map(); + private highlighter?: SiMarkdownHighlighter; + + constructor() { + this.installExtension(siMarkdownInlineHtml()).installExtension( + siMarkdownCode({ getHighlighter: () => this.getHighlighter() }) + ); + } + + /** + * Installs a plugin into the `unified` chain + * @param plugin - The `unified` plugin + * @param options - Options for the plugin + * @returns self for chaining + */ + installUnifiedPlugin(plugin: UnifiedPlugin, options?: any): SiMarkdownOptions { + this.plugins.push({ plugin, options }); + return this; + } + + /** + * Installs a extension which can contain plugins, type handlers + * @param extension - The extension definition + * @returns self for chaining + */ + installExtension(extension: SiMarkdownExtension): SiMarkdownOptions { + if (extension.plugins) { + this.plugins.push(...extension.plugins); + } + if (extension.types) { + this.types.push(...extension.types); + } + if (extension.codeTypes) { + for (const ct of extension.codeTypes) { + this.codeTypes.set(ct.type, ct); + } + } + return this; + } + + /** + * Sets the code highlighter + * @param highlighter - The highlighter + */ + setCodeHighlighter(highlighter?: SiMarkdownHighlighter): SiMarkdownOptions { + this.highlighter = highlighter; + return this; + } + + /** + * Creates the `unified` processor with all plugins and options + * @returns `unified` processor + */ + makeProcessor(): Processor { + const processor = unified().use(remarkParse).use(remarkGfm); + + for (const p of this.plugins) { + processor.use(p.plugin as Plugin, p.options); + } + + return processor; + } + + /** + * @returns All type handlers + */ + getTypeHandlers(): TypeHandler[] { + return this.types; + } + + /** + * @returns All code type handlers + */ + getCodeTypeHandlers(): Map { + return this.codeTypes; + } + + /** + * @returns The highlighter + */ + getHighlighter(): SiMarkdownHighlighter | undefined { + return this.highlighter; + } +} + +export const makeSiMarkdownOptions = (): SiMarkdownOptions => new SiMarkdownOptions(); diff --git a/projects/element-ng/markdown/si-markdown-template.directive.ts b/projects/element-ng/markdown/si-markdown-template.directive.ts new file mode 100644 index 0000000000..cbd38a2091 --- /dev/null +++ b/projects/element-ng/markdown/si-markdown-template.directive.ts @@ -0,0 +1,16 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { Directive, TemplateRef, inject, input } from '@angular/core'; + +/** Directive used to mark a template as a node type for rendering. */ +@Directive({ + selector: '[siMarkdownTemplate]' +}) +export class SiMarkdownTemplateDirective { + readonly template = inject>(TemplateRef); + // this should be required, but in some transitional state in the templates computed, this fails + /** @defaultValue '#dummy' */ + readonly nodeType = input('#dummy', { alias: 'siMarkdownTemplate' }); +} diff --git a/projects/element-ng/markdown/si-markdown-token.ts b/projects/element-ng/markdown/si-markdown-token.ts new file mode 100644 index 0000000000..c7464e0320 --- /dev/null +++ b/projects/element-ng/markdown/si-markdown-token.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { InjectionToken, Signal, TemplateRef } from '@angular/core'; + +/** + * Token to pass the si-markdown component w/o circular dependencies. + */ +export const SI_MARKDOWN_CONTROL = new InjectionToken('si.markdown.control'); + +/** + * Interface of si-markdown exposed to children. + */ +export interface SiMarkdownControl { + templates: Signal>>; + debug: Signal; +} diff --git a/projects/element-ng/markdown/si-markdown.component.html b/projects/element-ng/markdown/si-markdown.component.html new file mode 100644 index 0000000000..26d76900f4 --- /dev/null +++ b/projects/element-ng/markdown/si-markdown.component.html @@ -0,0 +1,153 @@ + + + +@let ast = tree(); +@if (ast) { + +} + + + @switch (node.depth) { + @case (1) { +

+ } + @case (2) { +

+ } + @case (3) { +

+ } + @case (4) { +

+ } + @case (5) { +
+ } + @case (6) { +
+ } + } +
+ + +

+
+ + +
+
+ + + @if (node.ordered) { +
    + } @else { +
      + } +
      + + +
    • + @if (node.checked !== null) { + + } +
    • +
      + + + + + + + + + + + + + + + + + + + + + + @for (cell of node.children[0].children; track $index) { + + } + + + + @for (row of node.children.slice(1); track $index) { + + @for (cell of row.children; track $index; let cellIndex = $index) { + + } + + } + +
      +
      + + + {{ node.value }} + + + + + + + +
      +
      + + +
      +
      + + +
      +
      + + + + {{ node.label }} + + + + + {{ node.value }} + + +@let conf = config(); +@let codeTypes = conf.codeTypeHandlers; +@for (handler of conf.typeHandlers; track handler.type) { + + @if (handler.type === 'code' && codeTypes.has(node.lang)) { + @let specialHandler = codeTypes.get(node.lang)!; + + } @else { + + } + +} diff --git a/projects/element-ng/markdown/si-markdown.component.spec.ts b/projects/element-ng/markdown/si-markdown.component.spec.ts new file mode 100644 index 0000000000..eb8ef012c7 --- /dev/null +++ b/projects/element-ng/markdown/si-markdown.component.spec.ts @@ -0,0 +1,111 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SiMarkdownComponent } from './si-markdown.component'; + +describe('SiMarkdownComponent', () => { + let fixture: ComponentFixture; + let element: HTMLElement; + + beforeEach(() => { + fixture = TestBed.createComponent(SiMarkdownComponent); + element = fixture.nativeElement; + }); + + it('does not render a fragment for empty markdown', async () => { + fixture.detectChanges(); + await fixture.whenStable(); + + expect(element.querySelector('si-markdown-fragment')).toBeNull(); + }); + + it('renders markdown using the built-in templates', async () => { + fixture.componentRef.setInput( + 'markdown', + `# Heading 1 +## Heading 2 +### Heading 3 +#### Heading 4 +##### Heading 5 +###### Heading 6 + +A paragraph with [a link](https://element.siemens.io "Element"), *emphasized*, **important**, and ~~deleted~~ text.[^1] + +> A quoted paragraph. + +- Unordered item +- [x] Completed task +- [ ] Open task + +3. Third item +4. Fourth item + +| Left | Center | Right | +| :--- | :----: | ---: | +| One | Two | Three | + +![Sample image](https://example.com/image.png "Sample title") + +--- + +First line +Second line +` + ); + fixture.detectChanges(); + // need a real tick here! + await new Promise(resolve => setTimeout(resolve)); + await fixture.whenStable(); + + expect(element.querySelector('h1')).toHaveTextContent('Heading 1'); + expect(element.querySelector('h2')).toHaveTextContent('Heading 2'); + expect(element.querySelector('h3')).toHaveTextContent('Heading 3'); + expect(element.querySelector('h4')).toHaveTextContent('Heading 4'); + expect(element.querySelector('h5')).toHaveTextContent('Heading 5'); + expect(element.querySelector('h6')).toHaveTextContent('Heading 6'); + expect(element.querySelector('p')).toHaveTextContent( + 'A paragraph with a link, emphasized, important, and deleted text.' + ); + expect(element.querySelector('blockquote')).toHaveTextContent('A quoted paragraph.'); + expect(element.querySelector('a')).toHaveAttribute('href', 'https://element.siemens.io'); + expect(element.querySelector('a')).toHaveAttribute('title', 'Element'); + expect(element.querySelector('em')).toHaveTextContent('emphasized'); + expect(element.querySelector('strong')).toHaveTextContent('important'); + expect(element.querySelector('del')).toHaveTextContent('deleted'); + expect(element.querySelector('ul')).toHaveTextContent('Unordered itemCompleted taskOpen task'); + expect(element.querySelector('ol')).toHaveAttribute('start', '3'); + expect(element.querySelectorAll('li')).toHaveLength(5); + expect(element.querySelectorAll('input[type="checkbox"]')).toHaveLength(2); + expect(element.querySelectorAll('input[type="checkbox"]')[0]).toBeChecked(); + expect(element.querySelectorAll('input[type="checkbox"]')[1]).not.toBeChecked(); + expect(element.querySelector('table')).toHaveClass('table', 'mb-6'); + expect(element.querySelectorAll('th')).toHaveLength(3); + expect(element.querySelectorAll('td')).toHaveLength(3); + expect(element.querySelectorAll('th')[0]).toHaveStyle({ textAlign: 'left' }); + expect(element.querySelectorAll('th')[1]).toHaveStyle({ textAlign: 'center' }); + expect(element.querySelectorAll('th')[2]).toHaveStyle({ textAlign: 'right' }); + expect(element.querySelector('img')).toHaveAttribute('src', 'https://example.com/image.png'); + expect(element.querySelector('img')).toHaveAttribute('alt', 'Sample image'); + expect(element.querySelector('img')).toHaveAttribute('title', 'Sample title'); + expect(element.querySelector('hr')).toBeInTheDocument(); + expect(element.querySelector('br')).toBeInTheDocument(); + }); + + it('renders sanitized inline HTML', async () => { + fixture.componentRef.setInput( + 'markdown', + '' + ); + fixture.detectChanges(); + await new Promise(resolve => setTimeout(resolve)); + await fixture.whenStable(); + + const unsafeImage = element.querySelector('.unsafe-html'); + + expect(unsafeImage).not.toBeNull(); + expect(unsafeImage).not.toHaveAttribute('onerror'); + }); +}); diff --git a/projects/element-ng/markdown/si-markdown.component.ts b/projects/element-ng/markdown/si-markdown.component.ts new file mode 100644 index 0000000000..7d41834904 --- /dev/null +++ b/projects/element-ng/markdown/si-markdown.component.ts @@ -0,0 +1,91 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { NgComponentOutlet } from '@angular/common'; +import { + Component, + input, + viewChildren, + contentChildren, + computed, + TemplateRef +} from '@angular/core'; +import { Root, type Node } from 'mdast'; +import { Processor } from 'unified'; + +import { SiMarkdownFragmentComponent } from './si-markdown-fragment.component'; +import { SiMarkdownOptions } from './si-markdown-options'; +import { SiMarkdownTemplateDirective } from './si-markdown-template.directive'; +import { SI_MARKDOWN_CONTROL, SiMarkdownControl } from './si-markdown-token'; +import { computedAsync, throttledSignal } from './utils/signal-utils'; + +/** + * Component to display markdown text + * @experimental + */ +@Component({ + selector: 'si-markdown', + imports: [NgComponentOutlet, SiMarkdownTemplateDirective, SiMarkdownFragmentComponent], + templateUrl: './si-markdown.component.html', + providers: [{ provide: SI_MARKDOWN_CONTROL, useExisting: SiMarkdownComponent }] +}) +export class SiMarkdownComponent implements SiMarkdownControl { + /** + * The markdown text to transform and display + * @defaultValue '' + */ + readonly markdown = input(''); + /** + * Options to control rendering. Can be shared across multiple instances. + */ + readonly options = input(); + /** + * Debug mode. When true, unknown node types will be displayed along with the node + * as JSON. + * @defaultValue false + */ + readonly debug = input(false); + + protected readonly tree = throttledSignal( + computedAsync({ + params: () => ({ markdown: this.markdown(), processor: this.config().processor }), + computation: params => this.parseMarkdown(params.markdown, params.processor), + initial: undefined + }), + 50 + ); + + protected readonly templateQuery = viewChildren(SiMarkdownTemplateDirective); + protected readonly customTemplateQuery = contentChildren(SiMarkdownTemplateDirective); + + /** @internal */ + readonly templates = computed(() => { + const map = new Map>(); + for (const template of this.templateQuery()) { + map.set(template.nodeType(), template.template); + } + for (const template of this.customTemplateQuery()) { + map.set(template.nodeType(), template.template); + } + return map; + }); + + private readonly actualOptions = computed(() => this.options() ?? new SiMarkdownOptions()); + protected readonly config = computed(() => { + const options = this.actualOptions(); + return { + processor: options.makeProcessor(), + typeHandlers: options.getTypeHandlers(), + codeTypeHandlers: options.getCodeTypeHandlers() + }; + }); + + private async parseMarkdown(md: string, processor: Processor): Promise { + if (!md) { + return undefined; + } + const parsed = processor.parse(md); + return await processor.run(parsed); + } +} diff --git a/projects/element-ng/markdown/si-markdown.types.ts b/projects/element-ng/markdown/si-markdown.types.ts new file mode 100644 index 0000000000..f1f0ddf6eb --- /dev/null +++ b/projects/element-ng/markdown/si-markdown.types.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { InputSignal, Type } from '@angular/core'; +import { type Node, type Parent } from 'mdast'; +import { type Transformer, type Plugin, type Preset, type PluggableList } from 'unified'; + +/** Interface an extension component must implement */ +export interface SiMarkdownExtensionComponent { + /** Node to be rendered */ + node: InputSignal; + /** The parent node */ + parent: InputSignal; + /* Options passed to the component */ + options: InputSignal; +} + +/** Interface an highlighter component must implement */ +export interface SiMarkdownHighlighterComponent { + /** The code to be highlighter */ + code: InputSignal; + /** The language */ + language: InputSignal; + /** Function the highlighter can call to updated the displayed language */ + updateLanguage: InputSignal<(lang?: string) => void>; + /* Options passed to the component */ + options: InputSignal; +} + +/** A combination of a component and options passed to it during run-time */ +export interface ComponentWithOptions { + /** the component used to render the node */ + component: Type; + /** options passed to the component */ + options?: any; +} + +/** AST node type handler */ +export interface TypeHandler extends ComponentWithOptions { + /** type of the AST node */ + type: string; +} + +export type SiMarkdownHighlighter = ComponentWithOptions; + +export type UnifiedPlugin = Plugin | Transformer | Preset | PluggableList; + +/** Combination of unified plugin with options */ +export interface PluginWithOptions { + plugin: UnifiedPlugin; + options?: any; +} + +/** + * An extension to the si-markdown component + */ +export interface SiMarkdownExtension { + /** `unified` plugins to install */ + plugins?: PluginWithOptions[]; + /** type handlers to install */ + types?: TypeHandler[]; + /** special code type handlers to install */ + codeTypes?: TypeHandler[]; +} diff --git a/projects/element-ng/markdown/utils/signal-utils.spec.ts b/projects/element-ng/markdown/utils/signal-utils.spec.ts new file mode 100644 index 0000000000..566da751ce --- /dev/null +++ b/projects/element-ng/markdown/utils/signal-utils.spec.ts @@ -0,0 +1,119 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ +import { signal, Signal } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; + +import { computedAsync, throttledSignal } from './signal-utils'; + +interface Deferred { + promise: Promise; + resolve: (value: T) => void; +} + +const deferred = (): Deferred => { + let resolve!: (value: T) => void; + const promise = new Promise(promiseResolve => (resolve = promiseResolve)); + return { promise, resolve }; +}; + +// Lets a resolved promise resume the await in computedAsync before flushing Angular effects. +const yieldMicrotask = (): Promise => Promise.resolve(); + +describe('signal utilities', () => { + describe('computedAsync', () => { + const source = signal('first'); + const computations = new Map>(); + let result: Signal; + + beforeEach(() => { + source.set('first'); + computations.clear(); + computations.set('first', deferred()); + result = TestBed.runInInjectionContext(() => + computedAsync({ + params: () => source(), + computation: currentSource => computations.get(currentSource)!.promise, + initial: 'initial' + }) + ); + TestBed.tick(); + }); + + it('retains its previous value while computing replacement params', async () => { + const first = computations.get('first')!; + first.resolve('first result'); + await yieldMicrotask(); + TestBed.tick(); + + computations.set('second', deferred()); + source.set('second'); + TestBed.tick(); + + expect(result()).toBe('first result'); + + computations.get('second')!.resolve('second result'); + await yieldMicrotask(); + TestBed.tick(); + + expect(result()).toBe('second result'); + }); + + it('ignores results from superseded computations', async () => { + computations.set('second', deferred()); + source.set('second'); + TestBed.tick(); + + computations.get('second')!.resolve('second result'); + await yieldMicrotask(); + TestBed.tick(); + + computations.get('first')!.resolve('stale first result'); + await yieldMicrotask(); + TestBed.tick(); + + expect(result()).toBe('second result'); + }); + }); + + describe('throttledSignal', () => { + const source = signal('initial'); + let result: Signal; + + beforeEach(() => { + vi.useFakeTimers(); + source.set('initial'); + result = TestBed.runInInjectionContext(() => throttledSignal(source, 100)); + TestBed.tick(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('immediately emits the first source change by default', () => { + source.set('first update'); + TestBed.tick(); + + expect(result()).toBe('first update'); + }); + + it('emits the latest source value once the interval elapses when immediate emission is disabled', () => { + result = TestBed.runInInjectionContext(() => throttledSignal(source, 100, false)); + TestBed.tick(); + + source.set('first update'); + TestBed.tick(); + vi.advanceTimersByTime(50); + + source.set('second update'); + TestBed.tick(); + vi.advanceTimersByTime(49); + expect(result()).toBe('initial'); + + vi.advanceTimersByTime(1); + expect(result()).toBe('second update'); + }); + }); +}); diff --git a/projects/element-ng/markdown/utils/signal-utils.ts b/projects/element-ng/markdown/utils/signal-utils.ts new file mode 100644 index 0000000000..434f0f5de0 --- /dev/null +++ b/projects/element-ng/markdown/utils/signal-utils.ts @@ -0,0 +1,101 @@ +/** + * Copyright (c) Siemens 2016 - 2026 + * SPDX-License-Identifier: MIT + */ + +import { effect, signal, Signal, untracked } from '@angular/core'; + +/** Options for `computedAsync()` */ +export interface ComputedAsyncOptions { + /** Params function, params will be passed to the `computation` function. This is tracked. */ + params: () => R; + /** Computation function. This is `untracked()`, params from the `params` function are passed */ + computation: (params: R) => Promise; + /** Initial value of the `computedAsync` signal. */ + initial: T; +} + +/** + * Computed value, but async. This exists because the `resource()` API is not suitable. During + * loading, the value changes to `undefined`. The proposed solution of resource snapshots and + * a `withPreviousValue()` function that uses `resourceFromSnapshots()` isn't suitable either + * because it's experimental. + * + * @param options - the options + * @returns readonly signal + */ +export const computedAsync = (options: ComputedAsyncOptions): Signal => { + const resultSignal = signal(options.initial); + + effect(async onCleanup => { + const params = options.params(); + + let destroyed = false; + untracked(async () => { + const result = await options.computation(params); + if (!destroyed) { + resultSignal.set(result); + } + }); + + onCleanup(() => (destroyed = true)); + }); + + return resultSignal.asReadonly(); +}; + +/** + * A readonly throttled version of a signal. Emits at most every `interval` milliseconds + * @param source - the source signal + * @param interval - the interval + * @param immediatelyEmitFirstChange - immediately emit the first change + * @returns A readonly signal + */ +export const throttledSignal = ( + source: Signal, + interval: number, + immediatelyEmitFirstChange = true +): Signal => { + const resultSignal = signal(source()); + let lastEmitted = 0; + let timer: ReturnType | null = null; + let firstChangeSeen = false; + + effect(onCleanup => { + const value = source(); + const now = Date.now(); + + if (!firstChangeSeen && immediatelyEmitFirstChange) { + firstChangeSeen = true; + resultSignal.set(value); + return; + } + + if (now - lastEmitted >= interval) { + resultSignal.set(value); + lastEmitted = now; + if (timer) { + clearTimeout(timer); + timer = null; + } + } else { + timer ??= setTimeout( + () => { + resultSignal.set(source()); + lastEmitted = Date.now(); + timer = null; + }, + interval - (now - lastEmitted) + ); + } + + onCleanup(() => { + if (timer) { + clearTimeout(timer); + timer = null; + } + }); + }); + + return resultSignal.asReadonly(); +}; diff --git a/projects/element-ng/package.json b/projects/element-ng/package.json index deb1bef7cc..5da23d242d 100644 --- a/projects/element-ng/package.json +++ b/projects/element-ng/package.json @@ -50,7 +50,14 @@ "ag-grid-community": "^34.3.1 || ^35.0.0 || ^36.0.0", "flag-icons": "^7.3.2", "google-libphonenumber": "^3.2.40", - "ngx-image-cropper": "^9.0.0" + "highlight.js": "^11.11.1", + "mermaid": "^11.16.0", + "katex": "^0.17.0", + "ngx-image-cropper": "^9.0.0", + "remark-gfm": "^4.0.1", + "remark-math": "^6.0.0", + "remark-parse": "^11.0.0", + "unified": "^11.0.5" }, "peerDependenciesMeta": { "@ngx-formly/bootstrap": { @@ -71,8 +78,29 @@ "google-libphonenumber": { "optional": true }, + "highlight.js": { + "optional": true + }, "ag-grid-community": { "optional": true + }, + "mermaid": { + "optional": true + }, + "katex": { + "optional": true + }, + "remark-gfm": { + "optional": true + }, + "remark-math": { + "optional": true + }, + "remark-parse": { + "optional": true + }, + "unified": { + "optional": true } } } diff --git a/projects/element-ng/summary-chip/si-summary-chip.component.html b/projects/element-ng/summary-chip/si-summary-chip.component.html index b9fec92820..e7124f8219 100644 --- a/projects/element-ng/summary-chip/si-summary-chip.component.html +++ b/projects/element-ng/summary-chip/si-summary-chip.component.html @@ -1,9 +1,9 @@